How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
我试图扩展这里给出的json.net示例http://james.newtonking.com/projects/json/help/customcreationconverter.html
我有另一个子类派生自基类/接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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(), }; |
如何反序列化以下JSON返回列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [ { "Department":"Department1", "JobTitle":"JobTitle1", "FirstName":"FirstName1", "LastName":"LastName1" }, { "Department":"Department2", "JobTitle":"JobTitle2", "FirstName":"FirstName2", "LastName":"LastName2" }, { "Skill":"Painter", "FirstName":"FirstName3", "LastName":"LastName3" } ] |
我不想使用类型名处理JSonserializerSettings。我专门寻找定制的JSonConverter实现来处理这个问题。关于这方面的文档和示例在网络上非常稀少。我似乎无法在jsonConverter中获得重写的readjson()方法实现。
使用标准的
我找到了一个关于类型转换的讨论线程,结果提供了答案。这里有一个链接:类型转换。
需要的是将
The JObject class provides a means to load a JSON object and
provides access to the data within this object.
重写的
然后,可以通过检查某些字段的存在性来分析该
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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; } } |
上述针对
这是我能想到的最好的解决上述实现中的一些问题的方法,但我仍然认为有些事情被忽视了:
更新我更新了这个,以有一个更明确的方法,使现有的读者副本。这只是封装了通过单个JSonReader设置进行复制的过程。理想情况下,此功能将在NewtonSoft库中维护,但现在,您可以使用以下功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /// <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="jObject">The jObject to create a new reader for.</param> /// <returns>The new disposable reader.</returns> 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; } |
应按如下方式使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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; } |
旧的解决方案如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | /// <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); } } |
我想我也会共享一个基于这个的解决方案,它使用反射与knownType属性一起工作,必须从任何基类中获取派生类,解决方案可以从递归中受益,以找到最佳匹配类,尽管我不需要它,在我的情况下,匹配是由给定给转换器的类型来完成的,如果它有knownType,它将sc在匹配JSON字符串中具有所有属性的类型之前,首先选择要匹配的类型。
使用简单如下:
1 2 | string json ="{ Name:"Something", LastName:"Otherthing" }"; var ret = JsonConvert.DeserializeObject<A>(json, new KnownTypeConverter()); |
在上述情况下,RET应为B型。
JSON类:
1 2 3 4 5 6 7 8 9 10 | [KnownType(typeof(B))] public class A { public string Name { get; set; } } public class B : A { public string LastName { get; set; } } |
转换器代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | /// <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(); } } |
JSonSubtypes项目实现了一个通用的转换器,它借助于属性来处理这个特性。
这里提供的混凝土样本是如何工作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | [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\ <div class="suo-content">[collapse title=""]<ul><li>非常有用的转换器。我自己就省了几个小时编写转换器的时间!</li></ul>[/collapse]</div><hr><P>这是图腾答案的扩展。它基本上做了相同的事情,但是属性匹配是基于序列化的JSON对象,而不是反映.NET对象。如果您正在使用[json属性]、使用camelcasepropertynamesControlResolver或执行任何其他操作,都会导致json与.NET对象不匹配,则这一点非常重要。</P><P>用法很简单:</P>[cc lang="csharp"][KnownType(typeof(B))] public class A { public string Name { get; set; } } public class B : A { public string LastName { get; set; } } |
转换器代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | /// <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; } } |
作为图腾已知类型解决方案的另一个变体,您可以使用反射来创建通用类型分解器,以避免使用已知类型属性。
这使用了一种类似于JuvalLowy的WCF通用解析器的技术。
只要您的基类是抽象的或接口,就可以自动确定已知的类型,而不必使用已知的类型属性进行修饰。
在我自己的例子中,我选择使用$type属性在我的JSON对象中指定类型,而不是尝试从属性中确定类型,尽管您可以从这里的其他解决方案中借用来使用基于属性的确定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | 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(); } |
然后可以将其安装为格式化程序
1 | GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonKnownTypeConverter()); |
A lot of the times the implementation will exist in the same namespace as the interface. So, I came up with this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | 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"); } } |
因此,您可以像这样在全球范围内包括:
1 2 3 4 5 6 7 |
这是另一个避免使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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); } } |