다음과 같은 수업이 있습니다.
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>
은는 generic이 ICollection<T>
아닌 non-generic 만 구현하기 때문 ICollection
입니다.
Json.Net을 사용하여이 문제를 해결하는 방법은 무엇입니까? (나는 이것을 잠시 검색했지만 내가 찾은 유일한 것은 복잡한 클래스 ICollection<T>
가 ConcurrentCollection
아닌 매핑에 관한 것 입니다.
최신 정보
릴리스 10.0.3부터 Json.NET은 ConcurrentBag<T>
. 릴리스 정보 에 따르면 :
원래 답변
당신이 추측으로 문제가 있다는 것입니다 ConcurrentBag<T>
구현은 ICollection
하고 IEnumerable<T>
있지만 ICollection<T>
Json.NET 읽기 전용 컬렉션으로 그것에 항목과 치료를 추가하는 방법을 알고하지 않도록. 입력 컬렉션을받는 매개 변수화 된 생성자ConcurrentBag<T>
가 있지만 Json.NET은 내부적으로 및 콜백을 가지고 있기 때문에 해당 생성자를 사용하지 않습니다 . Json.NET은 이러한 콜백이있을 때 매개 변수화 된 생성자를 사용하지 않고 대신 예외를 발생시킵니다.[OnSerializing]
[OnDeserialized]
Cannot call OnSerializing on an array or readonly list, or list created from a non-default constructor: System.Collections.Concurrent.ConcurrentBag`1[]
따라서 만들 필요가 사용자 정의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(),
};
예제 바이올린 .
즉, 위의 내용은 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);
}
}
}
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다