无法保留对数组或只读列表或从非默认构造函数创建的列表的引用 [英] Cannot preserve reference to array or readonly list, or list created from a non-default constructor

查看:48
本文介绍了无法保留对数组或只读列表或从非默认构造函数创建的列表的引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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[]>();

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

解决方案

Json.NET只是没有实现对只读集合和数组的引用的保留.异常消息中明确指出了这一点:

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

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

但是,数组的特殊之处在于它们只是半"只读的:它们在分配后无法调整大小,但是可以更改单个条目. (有关接口的讨论,请参见Array.IsReadOnly取决于接口实现可以利用这个事实来创建自定义JsonConverter 用于在读取过程中将JSON加载到中间JToken中的数组,通过查询令牌的内容分配大小正确的数组,然后将该数组添加到

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

然后像这样使用它:

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

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

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

I came across the below question which is mostly identical to the issue I am having:

JSON.NET cannot handle simple array deserialization?

However, my situation is slightly different. If I modify the Test class from that question to have an array property of the same type, I get the same deserialization error.

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[]>();

I bet I am missing an important attribute on the Tests property.

解决方案

Json.NET simply hasn't implemented preserving of references for read-only collections and arrays. This is explicitly stated in the exception message:

Newtonsoft.Json.JsonSerializationException: Cannot preserve reference to array or readonly list, or list created from a non-default constructor: Question41293407.Test[].

The reason that Newtonsoft has not implemented this is that their reference tracking functionality is intended to be capable of preserving recursive self references. Thus the object being deserialized must be allocated before reading its contents, so that nested back-references can be successfully resolved during content deserialization. However, a read-only collection can only be allocated after its contents have been read, since by definition it is read-only.

Arrays, however, are peculiar in that they are only "semi" read-only: they cannot be resized after being allocated, however individual entries can be changed. (see Array.IsReadOnly inconsistent depending on interface implementation for a discussion about this.) It's possible to take advantage of this fact to create a custom JsonConverter for arrays that, during reading, loads the JSON into an intermediate JToken, allocates an array of the correct size by querying the token's contents, adds the array to the serializer.ReferenceResolver, deserializes the contents into a list, then finally populates the array entries from the list:

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();
    }
}

The memory efficiency of this approach is not great, so for large collections it would be better to switch to a List<T>.

Then use it like:

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

Note the converter is completely generic and works for all arrays.

Sample fiddle showing successful deserialization of nested recursive self-references.

这篇关于无法保留对数组或只读列表或从非默认构造函数创建的列表的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆