【问题标题】:Json.net deserialize complex object with concurrent collection in compositionJson.net 反序列化复杂对象与组合中的并发集合
【发布时间】:2017-03-16 14:18:00
【问题描述】:

我有这样的课:

public class ComplexClass
{
    public ConcurrentBag<SimpleClass> _simpleClassObjects;
}

当我序列化这个类时,它可以工作。但是当我尝试反序列化时

public static ComplexClass LoadComplexClass()
    {
        ComplexClass persistedComplexClass;
        using (var stream = new StreamReader(File.Open(jsonFilePath, FileMode.Open)))
        {
            persistedComplexClass = (ComplexClass) JsonSerializer.Create().Deserialize(stream, typeof(ComplexClass));
        }
        return persistedComplexClass;
    }

它抛出异常:

Newtonsoft.Json.dll 中出现“System.InvalidCastException”类型的未处理异常

附加信息:无法将“System.Collections.Concurrent.ConcurrentBag`1[LabML.Model.Point]”类型的对象转换为“System.Collections.Generic.ICollection`1[LabML.Model.Point]”类型.

这个异常的根本原因是ConcurrentBag&lt;T&gt; 没有实现泛型ICollection&lt;T&gt;,只有非泛型ICollection

如何使用 Json.Net 解决这个问题? (我已经为此搜索了一段时间,但我发现的只是将 ICollection&lt;T&gt; 映射到 ConcurrentCollection 而不是复杂类。

【问题讨论】:

标签: c# json serialization json.net


【解决方案1】:

更新

从 10.0.3 版开始,Json.NET 声称可以正确序列化 ConcurrentBag&lt;T&gt;。根据release notes

  • 修复 - 修复序列化 ConcurrentStack/Queue/Bag

原答案

正如您推测的那样,问题在于 ConcurrentBag&lt;T&gt; 实现了 ICollectionIEnumerable&lt;T&gt; 但不是 ICollection&lt;T&gt; 所以 Json.NET 不知道如何向其中添加项目并将其视为只读集合。虽然ConcurrentBag&lt;T&gt; 确实有parameterized constructor taking an input collection,但Json.NET 不会使用该构造函数,因为它在内部也有[OnSerializing][OnDeserialized] 回调。当这些回调存在时,Json.NET 不会使用参数化构造函数,而是抛出异常

Cannot call OnSerializing on an array or readonly list, or list created from a non-default constructor: System.Collections.Concurrent.ConcurrentBag`1[]

因此有必要为ConcurrentBag&lt;T&gt;创建一个custom JsonConverter

public class ConcurrentBagConverter : ConcurrentBagConverterBase
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.GetConcurrentBagItemType() != null;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        try
        {
            var itemType = objectType.GetConcurrentBagItemType();
            var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            var genericMethod = method.MakeGenericMethod(new[] { objectType, itemType });
            return genericMethod.Invoke(this, new object[] { reader, objectType, itemType, existingValue, serializer });
        }
        catch (TargetInvocationException ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializationException
            throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        try
        {
            var itemType = objectType.GetConcurrentBagItemType();
            var method = GetType().GetMethod("WriteJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            var genericMethod = method.MakeGenericMethod(new[] { objectType, itemType });
            genericMethod.Invoke(this, new object[] { writer, value, serializer });
        }
        catch (TargetInvocationException ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializationException
            throw new JsonSerializationException("Failed to serialize " + objectType, ex);
        }
    }
}

public class ConcurrentBagConverter<TItem> : ConcurrentBagConverterBase
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(ConcurrentBagConverter<TItem>).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return ReadJsonGeneric<ConcurrentBag<TItem>, TItem>(reader, objectType, typeof(TItem), existingValue, serializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        WriteJsonGeneric<ConcurrentBag<TItem>, TItem>(writer, value, serializer);
    }
}

// https://stackoverflow.com/questions/42836648/json-net-deserialize-complex-object-with-concurrent-collection-in-composition
public abstract class ConcurrentBagConverterBase : JsonConverter
{
    protected TConcurrentBag ReadJsonGeneric<TConcurrentBag, TItem>(JsonReader reader, Type collectionType, Type itemType, object existingValue, JsonSerializer serializer)
        where TConcurrentBag : ConcurrentBag<TItem>
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("Expected {0}, encountered {1} at path {2}", JsonToken.StartArray, reader.TokenType, reader.Path));
        var collection = existingValue as TConcurrentBag ?? (TConcurrentBag)serializer.ContractResolver.ResolveContract(collectionType).DefaultCreator();
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.EndArray:
                    return collection;
                default:
                    collection.Add((TItem)serializer.Deserialize(reader, itemType));
                    break;
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
    }

    protected void WriteJsonGeneric<TConcurrentBag, TItem>(JsonWriter writer, object value, JsonSerializer serializer)
        where TConcurrentBag : ConcurrentBag<TItem>
    {
        // Snapshot the bag as an array and serialize the array.
        var array = ((TConcurrentBag)value).ToArray();
        serializer.Serialize(writer, array);
    }
}

internal static class TypeExtensions
{
    public static Type GetConcurrentBagItemType(this Type objectType)
    {
        while (objectType != null)
        {
            if (objectType.IsGenericType
                && objectType.GetGenericTypeDefinition() == typeof(ConcurrentBag<>))
            {
                return objectType.GetGenericArguments()[0];
            }
            objectType = objectType.BaseType;
        }
        return null;
    }
}

public class ConcurrentBagContractResolver : DefaultContractResolver
{
    protected override JsonArrayContract CreateArrayContract(Type objectType)
    {
        var contract = base.CreateArrayContract(objectType);

        var concurrentItemType = objectType.GetConcurrentBagItemType();
        if (concurrentItemType != null)
        {
            if (contract.Converter == null)
                contract.Converter = (JsonConverter)Activator.CreateInstance(typeof(ConcurrentBagConverter<>).MakeGenericType(new[] { concurrentItemType }));
        }

        return contract;
    }
}

然后,将通用版本应用于您的特定字段,如下所示:

public class ComplexClass
{
    [JsonConverter(typeof(ConcurrentBagConverter<SimpleClass>))]
    public ConcurrentBag<SimpleClass> _simpleClassObjects;
}

或者,使用以下设置为所有ConcurrentBag&lt;T&gt; 全局应用一个通用版本为任何T

var settings = new JsonSerializerSettings
{
    Converters = { new ConcurrentBagConverter() },
};

也可以使用自定义合约解析器,它的性能可能比使用通用转换器稍好:

var settings = new JsonSerializerSettings
{
    ContractResolver = new ConcurrentBagContractResolver(),
};

例如fiddle

话虽如此,上述方法仅在ConcurrentBag&lt;T&gt; 属性或字段为读/写时才有效。如果成员是只读的,那么我发现 Json.NET 9.0.1 将跳过反序列化即使存在转换器,因为它推断集合成员和内容都是只读的。 (这可能是JsonSerializerInternalReader.CalculatePropertyDetails() 中的一个错误。)

作为一种解决方法,您可以将属性设为可私下设置,并用[JsonProperty] 标记它:

public class ComplexClass
{
    ConcurrentBag<SimpleClass> m_simpleClassObjects = new ConcurrentBag<SimpleClass>();

    [JsonConverter(typeof(ConcurrentBagConverter<SimpleClass>))]
    [JsonProperty]
    public ConcurrentBag<SimpleClass> _simpleClassObjects { get { return m_simpleClassObjects; } private set { m_simpleClassObjects = value; } }
}

或使用代理数组属性,从而无需任何类型的转换器:

public class ComplexClass
{
    readonly ConcurrentBag<SimpleClass> m_simpleClassObjects = new ConcurrentBag<SimpleClass>();

    [JsonIgnore]
    public ConcurrentBag<SimpleClass> _simpleClassObjects { get { return m_simpleClassObjects; } }

    [JsonProperty("_simpleClassObjects")]
    SimpleClass[] _simpleClassObjectsArray
    {
        get
        {
            return _simpleClassObjects.ToArray();
        }
        set
        {
            if (value == null)
                return;
            foreach (var item in value)
                _simpleClassObjects.Add(item);
        }
    }
}

【讨论】:

  • 对理解、进阶和好的答案有很大帮助。谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-24
  • 2019-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多