更新
從版本10.0.3的,Json.NET聲稱正確地序列ConcurrentBag<T>
按照release notes:
- 修正 - 修正序列化ConcurrentStack /隊列/袋
原來的答案
正如你猜測,問題是ConcurrentBag<T>
實現ICollection
和IEnumerable<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 JsonConverter
爲ConcurrentBag<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);
}
}
}
看看定製的轉換:http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm –