C# 如何在JSON.NET中实现自定义JsonConverter以反序列化基类对象列表?
我试图扩展这里给出的JSON.net示例 我有另一个子类派生自基类/接口C# 如何在JSON.NET中实现自定义JsonConverter以反序列化基类对象列表?,c#,json,json.net,deserialization,C#,Json,Json.net,Deserialization,我试图扩展这里给出的JSON.net示例 我有另一个子类派生自基类/接口 public class Person { public string FirstName { get; set; } public string LastName { get; set; } } public class Employee : Person { public string Department { get; set; } public string JobTitle {
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Employee : Person
{
public string Department { get; set; }
public string JobTitle { get; set; }
}
public class Artist : Person
{
public string Skill { get; set; }
}
List<Person> people = new List<Person>
{
new Employee(),
new Employee(),
new Artist(),
};
我不想使用TypeNameHandling JsonSerializerSettings。我特别寻找定制JsonConverter实现来处理这个问题。关于这一点的文档和示例在网上非常稀少。我似乎无法在JsonConverter中正确实现重写的ReadJson()方法。使用标准的
CustomCreationConverter
,我正在努力工作如何生成正确的类型(Person
或Employee
),因为为了确定这一点,您需要分析JSON,并且没有内置的方法使用Create
方法进行分析
我发现了一个关于类型转换的讨论线程,结果它提供了答案。这里有一个链接:
所需的是子类化JsonConverter
,重写ReadJson
方法,并创建一个新的抽象Create
方法,该方法接受JObject
JObject类提供了加载JSON对象和
提供对此对象中的数据的访问
被重写的ReadJson
方法创建一个JObject
并调用Create
方法(由派生的转换器类实现),传入JObject
实例
然后可以通过检查某些字段的存在来分析此JObject
实例,以确定正确的类型
示例
string json = "[{
\"Department\": \"Department1\",
\"JobTitle\": \"JobTitle1\",
\"FirstName\": \"FirstName1\",
\"LastName\": \"LastName1\"
},{
\"Department\": \"Department2\",
\"JobTitle\": \"JobTitle2\",
\"FirstName\": \"FirstName2\",
\"LastName\": \"LastName2\"
},
{\"Skill\": \"Painter\",
\"FirstName\": \"FirstName3\",
\"LastName\": \"LastName3\"
}]";
List<Person> persons =
JsonConvert.DeserializeObject<List<Person>>(json, new PersonConverter());
...
public class PersonConverter : JsonCreationConverter<Person>
{
protected override Person Create(Type objectType, JObject jObject)
{
if (FieldExists("Skill", jObject))
{
return new Artist();
}
else if (FieldExists("Department", jObject))
{
return new Employee();
}
else
{
return new Person();
}
}
private bool FieldExists(string fieldName, JObject jObject)
{
return jObject[fieldName] != null;
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">
/// contents of JSON object that will be deserialized
/// </param>
/// <returns></returns>
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
}
stringjson=”[{
\“部门”:“部门1”,
\“JobTitle\:\“JobTitle1\”,
\“FirstName\:\“FirstName1\”,
\“LastName\”:\“LastName1”
},{
\“部门”:“部门2”,
\“JobTitle\:\“JobTitle2\”,
\“FirstName\:\“FirstName2\”,
\“LastName\”:\“LastName 2”
},
{\'Skill\':\'Painter\',
\“FirstName\:\“FirstName3\”,
\“LastName\”:\“LastName 3”
}]";
名单人员=
反序列化对象(json,newPersonConverter());
...
公共类PersonConverter:JsonCreationConverter
{
受保护的重写人员创建(类型objectType,JObject JObject)
{
如果(字段存在(“技能”,作业对象))
{
返回新的艺术家();
}
否则(字段存在(“部门”,作业对象))
{
返回新员工();
}
其他的
{
返回新人();
}
}
私有布尔字段存在(字符串字段名,JObject JObject)
{
返回jObject[fieldName]!=null;
}
}
公共抽象类JsonCreationConverter:JsonConverter
{
///
///在JSON对象中创建基于属性的objectType实例
///
///预期的对象类型
///
///将被反序列化的JSON对象的内容
///
///
受保护的抽象T Create(类型objectType,JObject-JObject);
公共覆盖布尔CanConvert(类型objectType)
{
返回typeof(T).IsAssignableFrom(objectType);
}
公共覆盖布尔可写
{
获取{return false;}
}
公共重写对象ReadJson(JsonReader,
类型objectType,
对象现有值,
JsonSerializer(序列化程序)
{
//从流中加载作业对象
JObject=JObject.Load(读卡器);
//基于JObject创建目标对象
T target=Create(objectType,jObject);
//填充对象属性
填充(jObject.CreateReader(),目标);
回报目标;
}
}
我想我也会分享一个基于此的解决方案,它使用反射与Knowntype属性一起工作,必须从任何基类中获取派生类,解决方案可以从递归中受益,以找到最佳匹配类,尽管我的情况下不需要它,匹配是由给定给转换器的类型完成的,如果它有KnownTypes,它将扫描所有这些类型,直到它匹配一个包含json字符串中所有属性的类型,将选择第一个匹配的类型
用法非常简单,如下所示:
string json = "{ Name:\"Something\", LastName:\"Otherthing\" }";
var ret = JsonConvert.DeserializeObject<A>(json, new KnownTypeConverter());
转换器代码:
/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(objectType); // Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is KnownTypeAttribute)
{
KnownTypeAttribute k = (KnownTypeAttribute) attr;
var props = k.Type.GetProperties();
bool found = true;
foreach (var f in jObject)
{
if (!props.Any(z => z.Name == f.Key))
{
found = false;
break;
}
}
if (found)
{
var target = Activator.CreateInstance(k.Type);
serializer.Populate(jObject.CreateReader(),target);
return target;
}
}
}
throw new ObjectNotFoundException();
// Populate the object properties
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter {
public override bool CanConvert( Type objectType ) {
return System.Attribute.GetCustomAttributes( objectType ).Any( v => v is KnownTypeAttribute );
}
public override bool CanWrite {
get { return false; }
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
// Load JObject from stream
JObject jObject = JObject.Load( reader );
// Create target object based on JObject
System.Attribute[ ] attrs = System.Attribute.GetCustomAttributes( objectType ); // Reflection.
// check known types for a match.
foreach( var attr in attrs.OfType<KnownTypeAttribute>( ) ) {
object target = Activator.CreateInstance( attr.Type );
JObject jTest;
using( var writer = new StringWriter( ) ) {
using( var jsonWriter = new JsonTextWriter( writer ) ) {
serializer.Serialize( jsonWriter, target );
string json = writer.ToString( );
jTest = JObject.Parse( json );
}
}
var jO = this.GetKeys( jObject ).Select( k => k.Key ).ToList( );
var jT = this.GetKeys( jTest ).Select( k => k.Key ).ToList( );
if( jO.Count == jT.Count && jO.Intersect( jT ).Count( ) == jO.Count ) {
serializer.Populate( jObject.CreateReader( ), target );
return target;
}
}
throw new SerializationException( string.Format( "Could not convert base class {0}", objectType ) );
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) {
throw new NotImplementedException( );
}
private IEnumerable<KeyValuePair<string, JToken>> GetKeys( JObject obj ) {
var list = new List<KeyValuePair<string, JToken>>( );
foreach( var t in obj ) {
list.Add( t );
}
return list;
}
}
//
///使用KnownType属性根据给定给Serializer的类匹配divierd类
///所选类将是第一个匹配json对象中所有属性的类。
///
公共类KnownTypeConverter:JsonConverter
{
公共覆盖布尔CanConvert(类型objectType)
{
返回System.Attribute.GetCustomAttributes(objectType).Any(v=>v为KnownTypeAttribute);
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
//从流中加载作业对象
JObject=JObject.Load(读卡器);
//基于JObject创建目标对象
System.Attribute[]attrs=System.Attribute.GetCustomAttributes(objectType);//反射。
//显示输出。
foreach(attrs中的System.Attribute attr)
{
if(属性为KnownTypeAttribute)
{
KnownTypeAttribute k=(KnownTypeAttribute)属性;
var props=k.Type.GetProperties();
布尔发现=真;
foreach(jObject中的var f)
{
如果(!props.Any(z=>z.Name==f.Key))
{
发现=错误;
打破
}
}
如果(找到)
{
var target=Activator.CreateInstance(k.Type);
ser
/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(objectType); // Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is KnownTypeAttribute)
{
KnownTypeAttribute k = (KnownTypeAttribute) attr;
var props = k.Type.GetProperties();
bool found = true;
foreach (var f in jObject)
{
if (!props.Any(z => z.Name == f.Key))
{
found = false;
break;
}
}
if (found)
{
var target = Activator.CreateInstance(k.Type);
serializer.Populate(jObject.CreateReader(),target);
return target;
}
}
}
throw new ObjectNotFoundException();
// Populate the object properties
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
/// <summary>Creates a new reader for the specified jObject by copying the settings
/// from an existing reader.</summary>
/// <param name="reader">The reader whose settings should be copied.</param>
/// <param name="jToken">The jToken to create a new reader for.</param>
/// <returns>The new disposable reader.</returns>
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
JsonReader jTokenReader = jToken.CreateReader();
jTokenReader.Culture = reader.Culture;
jTokenReader.DateFormatString = reader.DateFormatString;
jTokenReader.DateParseHandling = reader.DateParseHandling;
jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jTokenReader.FloatParseHandling = reader.FloatParseHandling;
jTokenReader.MaxDepth = reader.MaxDepth;
jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
return jTokenReader;
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
{
serializer.Populate(jObjectReader, target);
}
return target;
}
/// <summary>Base Generic JSON Converter that can help quickly define converters for specific types by automatically
/// generating the CanConvert, ReadJson, and WriteJson methods, requiring the implementer only to define a strongly typed Create method.</summary>
public abstract class JsonCreationConverter<T> : JsonConverter
{
/// <summary>Create an instance of objectType, based properties in the JSON object</summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">contents of JSON object that will be deserialized</param>
protected abstract T Create(Type objectType, JObject jObject);
/// <summary>Determines if this converted is designed to deserialization to objects of the specified type.</summary>
/// <param name="objectType">The target type for deserialization.</param>
/// <returns>True if the type is supported.</returns>
public override bool CanConvert(Type objectType)
{
// FrameWork 4.5
// return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
// Otherwise
return typeof(T).IsAssignableFrom(objectType);
}
/// <summary>Parses the json to the specified type.</summary>
/// <param name="reader">Newtonsoft.Json.JsonReader</param>
/// <param name="objectType">Target type.</param>
/// <param name="existingValue">Ignored</param>
/// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
/// <returns>Deserialized Object</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
/// <summary>Serializes to the specified type</summary>
/// <param name="writer">Newtonsoft.Json.JsonWriter</param>
/// <param name="value">Object to serialize.</param>
/// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
StringWriter writer = new StringWriter();
serializer.Serialize(writer, jObject);
using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString())))
{
newReader.Culture = reader.Culture;
newReader.DateParseHandling = reader.DateParseHandling;
newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
newReader.FloatParseHandling = reader.FloatParseHandling;
serializer.Populate(newReader, target);
}
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
[KnownType(typeof(B))]
public class A
{
public string Name { get; set; }
}
public class B : A
{
public string LastName { get; set; }
}
/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter {
public override bool CanConvert( Type objectType ) {
return System.Attribute.GetCustomAttributes( objectType ).Any( v => v is KnownTypeAttribute );
}
public override bool CanWrite {
get { return false; }
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
// Load JObject from stream
JObject jObject = JObject.Load( reader );
// Create target object based on JObject
System.Attribute[ ] attrs = System.Attribute.GetCustomAttributes( objectType ); // Reflection.
// check known types for a match.
foreach( var attr in attrs.OfType<KnownTypeAttribute>( ) ) {
object target = Activator.CreateInstance( attr.Type );
JObject jTest;
using( var writer = new StringWriter( ) ) {
using( var jsonWriter = new JsonTextWriter( writer ) ) {
serializer.Serialize( jsonWriter, target );
string json = writer.ToString( );
jTest = JObject.Parse( json );
}
}
var jO = this.GetKeys( jObject ).Select( k => k.Key ).ToList( );
var jT = this.GetKeys( jTest ).Select( k => k.Key ).ToList( );
if( jO.Count == jT.Count && jO.Intersect( jT ).Count( ) == jO.Count ) {
serializer.Populate( jObject.CreateReader( ), target );
return target;
}
}
throw new SerializationException( string.Format( "Could not convert base class {0}", objectType ) );
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) {
throw new NotImplementedException( );
}
private IEnumerable<KeyValuePair<string, JToken>> GetKeys( JObject obj ) {
var list = new List<KeyValuePair<string, JToken>>( );
foreach( var t in obj ) {
list.Add( t );
}
return list;
}
}
public class JsonKnownTypeConverter : JsonConverter
{
public IEnumerable<Type> KnownTypes { get; set; }
public JsonKnownTypeConverter() : this(ReflectTypes())
{
}
public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
{
KnownTypes = knownTypes;
}
protected object Create(Type objectType, JObject jObject)
{
if (jObject["$type"] != null)
{
string typeName = jObject["$type"].ToString();
return Activator.CreateInstance(KnownTypes.First(x => typeName == x.Name));
}
else
{
return Activator.CreateInstance(objectType);
}
throw new InvalidOperationException("No supported type");
}
public override bool CanConvert(Type objectType)
{
if (KnownTypes == null)
return false;
return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
var target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
//Static helpers
static Assembly CallingAssembly = Assembly.GetCallingAssembly();
static Type[] ReflectTypes()
{
List<Type> types = new List<Type>();
var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
foreach (var assemblyName in referencedAssemblies)
{
Assembly assembly = Assembly.Load(assemblyName);
Type[] typesInReferencedAssembly = GetTypes(assembly);
types.AddRange(typesInReferencedAssembly);
}
return types.ToArray();
}
static Type[] GetTypes(Assembly assembly, bool publicOnly = true)
{
Type[] allTypes = assembly.GetTypes();
List<Type> types = new List<Type>();
foreach (Type type in allTypes)
{
if (type.IsEnum == false &&
type.IsInterface == false &&
type.IsGenericTypeDefinition == false)
{
if (publicOnly == true && type.IsPublic == false)
{
if (type.IsNested == false)
{
continue;
}
if (type.IsNestedPrivate == true)
{
continue;
}
}
types.Add(type);
}
}
return types.ToArray();
}
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonKnownTypeConverter());
[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Employee : Person
{
public string Department { get; set; }
public string JobTitle { get; set; }
}
public class Artist : Person
{
public string Skill { get; set; }
}
[TestMethod]
public void Demo()
{
string json = "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
"{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
"{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";
var persons = JsonConvert.DeserializeObject<IReadOnlyCollection<Person>>(json);
Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
}
public class InterfaceConverter : JsonConverter
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.ReadFrom(reader);
var typeVariable = this.GetTypeVariable(token);
if (TypeExtensions.TryParse(typeVariable, out var implimentation))
{ }
else if (!typeof(IEnumerable).IsAssignableFrom(objectType))
{
implimentation = this.GetImplimentedType(objectType);
}
else
{
var genericArgumentTypes = objectType.GetGenericArguments();
var innerType = genericArgumentTypes.FirstOrDefault();
if (innerType == null)
{
implimentation = typeof(IEnumerable);
}
else
{
Type genericType = null;
if (token.HasAny())
{
var firstItem = token[0];
var genericTypeVariable = this.GetTypeVariable(firstItem);
TypeExtensions.TryParse(genericTypeVariable, out genericType);
}
genericType = genericType ?? this.GetImplimentedType(innerType);
implimentation = typeof(IEnumerable<>);
implimentation = implimentation.MakeGenericType(genericType);
}
}
return JsonConvert.DeserializeObject(token.ToString(), implimentation);
}
public override bool CanConvert(Type objectType)
{
return !typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.IsInterface || typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.GetGenericArguments().Any(t => t.IsInterface);
}
protected Type GetImplimentedType(Type interfaceType)
{
if (!interfaceType.IsInterface)
{
return interfaceType;
}
var implimentationQualifiedName = interfaceType.AssemblyQualifiedName?.Replace(interfaceType.Name, interfaceType.Name.Substring(1));
return implimentationQualifiedName == null ? interfaceType : Type.GetType(implimentationQualifiedName) ?? interfaceType;
}
protected string GetTypeVariable(JToken token)
{
if (!token.HasAny())
{
return null;
}
return token.Type != JTokenType.Object ? null : token.Value<string>("$type");
}
}
public static JsonSerializerSettings StandardSerializerSettings => new JsonSerializerSettings
{
Converters = new List<JsonConverter>
{
new InterfaceConverter()
}
};
{
"documents": [
{
"document_id": "76b7be75-f4dc-44cd-90d2-0d1959922852",
"date": "2019-12-10 11:32:49",
"processed_date": "2019-12-10 11:32:49",
"sender_id": "9dedee17-e43a-47f1-910e-3a88ff6bc258",
},
{
"document_id": "5044a9ac-0314-4e9a-9e0c-817531120753",
"date": "2019-12-10 11:32:44",
"processed_date": "2019-12-10 11:32:44",
}
],
"total": 2
}
/// <summary>
/// Service response model
/// </summary>
public class DocumentsRequestIdResponse
{
[JsonProperty("documents")]
public Document[] Documents { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
}
// <summary>
/// Base document
/// </summary>
[JsonConverter(typeof(KnownTypeConverter))]
[KnownType(typeof(OutgoingDocument))]
[KnownType(typeof(IncomingDocument))]
public class Document
{
[JsonProperty("document_id")]
public Guid DocumentId { get; set; }
[JsonProperty("date")]
public DateTime Date { get; set; }
[JsonProperty("processed_date")]
public DateTime ProcessedDate { get; set; }
}
/// <summary>
/// Outgoing document
/// </summary>
public class OutgoingDocument : Document
{
// this property is optional and may not be present in the service's json response
[JsonProperty("device_id")]
public string DeviceId { get; set; }
// this property is optional and may not be present in the service's json response
[JsonProperty("msg_id")]
public string MsgId { get; set; }
}
/// <summary>
/// Incoming document
/// </summary>
public class IncomingDocument : Document
{
// this property is mandatory and is always populated by the service
[JsonProperty("sender_sys_id")]
public Guid SenderSysId { get; set; }
}
public class KnownTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
}
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// load the object
JObject jObject = JObject.Load(reader);
// take custom attributes on the type
Attribute[] attrs = Attribute.GetCustomAttributes(objectType);
Type mostSuitableType = null;
int countOfMaxMatchingProperties = -1;
// take the names of elements from json data
HashSet<string> jObjectKeys = GetKeys(jObject);
// take the properties of the parent class (in our case, from the Document class, which is specified in DocumentsRequestIdResponse)
HashSet<string> objectTypeProps = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(p => p.Name)
.ToHashSet();
// trying to find the right "KnownType"
foreach (var attr in attrs.OfType<KnownTypeAttribute>())
{
Type knownType = attr.Type;
if(!objectType.IsAssignableFrom(knownType))
continue;
// select properties of the inheritor, except properties from the parent class and properties with "ignore" attributes (in our case JsonIgnoreAttribute and XmlIgnoreAttribute)
var notIgnoreProps = knownType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => !objectTypeProps.Contains(p.Name)
&& p.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute) && a.AttributeType != typeof(System.Xml.Serialization.XmlIgnoreAttribute)));
// get serializable property names
var jsonNameFields = notIgnoreProps.Select(prop =>
{
string jsonFieldName = null;
CustomAttributeData jsonPropertyAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(JsonPropertyAttribute));
if (jsonPropertyAttribute != null)
{
// take the name of the json element from the attribute constructor
CustomAttributeTypedArgument argument = jsonPropertyAttribute.ConstructorArguments.FirstOrDefault();
if(argument != null && argument.ArgumentType == typeof(string) && !string.IsNullOrEmpty((string)argument.Value))
jsonFieldName = (string)argument.Value;
}
// otherwise, take the name of the property
if (string.IsNullOrEmpty(jsonFieldName))
{
jsonFieldName = prop.Name;
}
return jsonFieldName;
});
HashSet<string> jKnownTypeKeys = new HashSet<string>(jsonNameFields);
// by intersecting the sets of names we determine the most suitable inheritor
int count = jObjectKeys.Intersect(jKnownTypeKeys).Count();
if (count == jKnownTypeKeys.Count)
{
mostSuitableType = knownType;
break;
}
if (count > countOfMaxMatchingProperties)
{
countOfMaxMatchingProperties = count;
mostSuitableType = knownType;
}
}
if (mostSuitableType != null)
{
object target = Activator.CreateInstance(mostSuitableType);
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
{
serializer.Populate(jObjectReader, target);
}
return target;
}
throw new SerializationException($"Could not serialize to KnownTypes and assign to base class {objectType} reference");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
private HashSet<string> GetKeys(JObject obj)
{
return new HashSet<string>(((IEnumerable<KeyValuePair<string, JToken>>) obj).Select(k => k.Key));
}
public static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject)
{
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateFormatString = reader.DateFormatString;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
jObjectReader.MaxDepth = reader.MaxDepth;
jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
return jObjectReader;
}
}