2017-03-16 61 views
3

我有一類這樣的:Json.net反序列化與併發收集複雜對象的組成

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.Collections.Concurrent.ConcurrentBag`1的類型轉換對象[LabML.Model.Point]'來鍵入'System.Collections.Generic.ICollection`1 [LabML.Model.Point]'。

這此異常的根本原因是,ConcurrentBag<T>不實現通用ICollection<T>,只有非通用ICollection

如何使用Json.Net解決此問題? (我搜索了一會兒這一點,但只有我發現了什麼是關於ICollection<T>ConcurrentCollection不是在複雜的類映射。

+0

看看定製的轉換:http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm –

回答

2

更新

從版本10.0.3的,Json.NET聲稱正確地序列ConcurrentBag<T>按照release notes

  • 修正 - 修正序列化ConcurrentStack /隊列/袋

原來的答案

正如你猜測,問題是ConcurrentBag<T>實現ICollectionIEnumerable<T>但不ICollection<T>所以Json.NET不知道如何將項目添加到它,並把它當作一個只讀集合。雖然ConcurrentBag<T>確實有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[] 

因此,有必要建立一個custom JsonConverterConcurrentBag<T>

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<T> fo [R任何T使用以下設置:

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

或者可以使用自定義的合同解析器,這可能比使用通用型變頻器表現稍好:

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

fiddle

這就是說,上述只有在ConcurrentBag<T>屬性或字段是可讀/寫的情況下才有效。如果該成員是隻讀的,那麼我發現即使轉換器存在,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); 
     } 
    } 
} 
+0

幫助了很多的理解,先進和很好的答案。謝謝! – drqCode