gpt4 book ai didi

c# - Json.net反序列化具有并发集合的复杂对象

转载 作者:行者123 更新时间:2023-11-30 16:45:04 27 4
gpt4 key购买 nike

我有这样一个类:

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

它抛出异常:

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]'.

此异常的根本原因是 ConcurrentBag<T>不实现通用 ICollection<T> , 只有非通用 ICollection .

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

最佳答案

更新

从 10.0.3 版开始,Json.NET 声称可以正确序列化 ConcurrentBag<T> .根据release notes :

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

原始答案

如您所料,问题在于 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 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> 全局应用通用版本对于任何 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() 中的错误。)

作为一种解决方法,您可以使该属性可私有(private)设置,并用 [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);
}
}
}

关于c# - Json.net反序列化具有并发集合的复杂对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42836648/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com