无法保留对数组或只读列表或从非默认构造函数创建的列表的引用

osc*_*tin 4 .net serialization json.net

我遇到了以下问题,该问题与我遇到的问题大体相同:

JSON.NET无法处理简单的数组反序列化?

但是,我的情况略有不同。如果我修改该Test问题的类以使其具有相同类型的array属性,则会收到相同的反序列化错误。

class Test
{
    public Test[] Tests;
}

var settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.All
};

var o = new Test { Tests = new[] { new Test(), new Test() } };
//var o = new Test(); //this works if I leave the Tests array property null
var arr = new[] { o, o };
var ser = JsonConvert.SerializeObject(arr, settings);

arr = ((JArray)JsonConvert.DeserializeObject(ser, settings)).ToObject<Test[]>();
Run Code Online (Sandbox Code Playgroud)

我敢打赌,我缺少该属性的重要属性Tests

dbc*_*dbc 6

Json.NET根本没有实现对只读集合和数组的引用的保留。这在异常消息中明确声明:

Newtonsoft.Json.JsonSerializationException:无法保留对数组或只读列表或通过非默认构造函数创建的列表的引用:Question41293407.Test []。

Newtonsoft尚未实现此功能的原因是其引用跟踪功能旨在保留递归的自引用。因此,要反序列化的对象必须在读取其内容之前进行分配,以便可以在内容反序列化期间成功解析嵌套的反向引用。但是,只读集合只能其内容读取分配,因为根据定义,它是只读的。

但是,数组的特殊之处在于它们只是“半”只读的:它们在分配后无法调整大小,但是可以更改单个条目。(有关此问题的讨论,请参见Array.IsReadOnly不一致,具体取决于接口实现。)可以利用这一事实为数组创建自定义JsonConverter,该数组在读取期间将JSON加载到中间层中JToken,并分配正确大小的数组通过查询令牌的内容,将数组添加到中serializer.ReferenceResolver,将内容反序列化为列表,然后最后从列表中填充数组条目:

public class ArrayReferencePreservngConverter : JsonConverter
{
    const string refProperty = "$ref";
    const string idProperty = "$id";
    const string valuesProperty = "$values";

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsArray;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType == JsonToken.StartArray)
        {
            // No $ref.  Deserialize as a List<T> to avoid infinite recursion and return as an array.
            var elementType = objectType.GetElementType();
            var listType = typeof(List<>).MakeGenericType(elementType);
            var list = (IList)serializer.Deserialize(reader, listType);
            if (list == null)
                return null;
            var array = Array.CreateInstance(elementType, list.Count);
            list.CopyTo(array, 0);
            return array;
        }
        else
        {
            var obj = JObject.Load(reader);
            var refId = (string)obj[refProperty];
            if (refId != null)
            {
                var reference = serializer.ReferenceResolver.ResolveReference(serializer, refId);
                if (reference != null)
                    return reference;
            }
            var values = obj[valuesProperty];
            if (values == null || values.Type == JTokenType.Null)
                return null;
            if (!(values is JArray))
            {
                throw new JsonSerializationException(string.Format("{0} was not an array", values));
            }
            var count = ((JArray)values).Count;

            var elementType = objectType.GetElementType();
            var array = Array.CreateInstance(elementType, count);

            var objId = (string)obj[idProperty];
            if (objId != null)
            {
                // Add the empty array into the reference table BEFORE poppulating it,
                // to handle recursive references.
                serializer.ReferenceResolver.AddReference(serializer, objId, array);
            }

            var listType = typeof(List<>).MakeGenericType(elementType);
            using (var subReader = values.CreateReader())
            {
                var list = (IList)serializer.Deserialize(subReader, listType);
                list.CopyTo(array, 0);
            }

            return array;
        }
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法的内存效率不是很高,因此对于大型集合,最好切换到List<T>

然后像这样使用它:

var settings = new JsonSerializerSettings
{
    Converters = { new ArrayReferencePreservngConverter() },
    PreserveReferencesHandling = PreserveReferencesHandling.All
};
var a2 = JsonConvert.DeserializeObject<Test[]>(jsonString, settings);
Run Code Online (Sandbox Code Playgroud)

请注意,转换器是完全通用的,适用于所有阵列。

示例小提琴显示了嵌套递归自引用的成功反序列化。