Json.net使用组合中的并发集合反序列化复杂对象(Json.net deserialize complex object with concurrent collection in composi

编程入门 行业动态 更新时间:2024-10-23 17:28:03
Json.net使用组合中的并发集合反序列化复杂对象(Json.net deserialize complex object with concurrent collection in composition)

我有一个这样的课:

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<T>不实现通用ICollection<T> ,只实现非通用ICollection 。

如何使用Json.Net解决这个问题? (我已经搜索了一段时间,但只有我发现的是将ICollection<T>映射到不在复杂类中的ConcurrentCollection 。

I have a class like this:

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

When i serialize this class, it works. But when i try to deserialize

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

it throws the exception:

An unhandled exception of type 'System.InvalidCastException' occurred in Newtonsoft.Json.dll

Additional information: Unable to cast object of type 'System.Collections.Concurrent.ConcurrentBag`1[LabML.Model.Point]' to type 'System.Collections.Generic.ICollection`1[LabML.Model.Point]'.

This root cause of this exception is that the ConcurrentBag<T> doesn't implements generic ICollection<T>, only non-generic ICollection.

How to resolve this using Json.Net? (I've searched a while for this, but only what i found is about mapping an ICollection<T> to ConcurrentCollection not in Complex Classes.

最满意答案

更新

从版本10.0.3开始,Json.NET声称正确序列化ConcurrentBag<T> 。 根据发布说明 :

修复 - 修复了序列化ConcurrentStack / Queue / Bag的问题

原始答案

正如你猜测的那样,问题是ConcurrentBag<T>实现ICollection和IEnumerable<T>而不是ICollection<T>所以Json.NET不知道如何向它添加项目并将其视为只读集合。 虽然ConcurrentBag<T>确实有一个参数化构造函数来获取输入集合 ,但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<T>创建自定义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; }

或者,使用以下设置为任何T所有ConcurrentBag<T>全局应用通用版本:

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

或者,可以使用自定义合约解析器,其性能可能比使用通用转换器稍好一些:

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

示例小提琴 。

话虽如此,上述仅在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); } } }

Update

As of Release 10.0.3, Json.NET claims to correctly serialize ConcurrentBag<T>. According to the release notes:

Fix - Fixed serializing ConcurrentStack/Queue/Bag

Original Answer

As you surmise, the problem is that ConcurrentBag<T> implements ICollection and IEnumerable<T> but not ICollection<T> so Json.NET does not know how to add items to it and treats it as a read-only collection. While ConcurrentBag<T> does have a parameterized constructor taking an input collection, Json.NET will not use that constructor because it also, internally, has [OnSerializing] and [OnDeserialized] callbacks. Json.NET will not use a parameterized constructor when these callbacks are present, instead throwing an exception

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

Thus it is necessary to create a custom JsonConverter for 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; } }

Then, apply the generic version to your specific field as follows:

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

Or, apply a universal version globally for all ConcurrentBag<T> for any T using the following settings:

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

Alternatively a custom contract resolver could be used, which might have slightly better performance than using the universal converter:

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

Example fiddle.

That being said, the above only work if the ConcurrentBag<T> property or field is read/write. If the member is read-only then I have found that Json.NET 9.0.1 will skip deserialization even if a converter is present because it infers that the collection member and contents are both read-only. (This may be a bug in JsonSerializerInternalReader.CalculatePropertyDetails().)

As a workaround, you could make the property be privately settable, and mark it with [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; } } }

Or use a surrogate array property, thereby eliminating the need for any sort of converter:

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

更多推荐

本文发布于:2023-08-07 11:46:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1464256.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:组合   对象   序列化   Json   net

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!