如何在JSON.NET中实现自定义的JsonConverter来反序列化基类对象列表?
我想扩展这里给出的JSON.net的例子http://james.newtonking.com/projects/json/help/CustomCreationConverter.html
我有另一个从基类/接口派生的子类
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反序列化回List <Person>
[ { "Department": "Department1", "JobTitle": "JobTitle1", "FirstName": "FirstName1", "LastName": "LastName1" }, { "Department": "Department2", "JobTitle": "JobTitle2", "FirstName": "FirstName2", "LastName": "LastName2" }, { "Skill": "Painter", "FirstName": "FirstName3", "LastName": "LastName3" } ]
我不想使用TypeNameHandling JsonSerializerSettings。 我特别寻找自定义的JsonConverter实现来处理这个。 网上的文档和例子在网上很less见。 我似乎无法得到在JsonConverter权重覆盖的ReadJson()方法的实现。
使用标准的CustomCreationConverter
,我正在努力工作如何生成正确的types( Person
或Employee
),因为为了确定这一点,你需要分析JSON,并没有内置的方法来使用Create
方法来做到这一点。
我发现了一个关于types转换的讨论线索,结果是提供了答案。 这是一个链接: types转换 。
所需要的是JsonConverter
,覆盖ReadJson
方法并创build一个接受JObject
的新的抽象Create
方法。
JObject类提供了加载JSON对象的方法,并提供对该对象内数据的访问。
重写的ReadJson
方法创build一个JObject
并调用Create
方法(由派生的转换器类实现),传入JObject
实例。
然后可以分析此JObject
实例,以通过检查某些字段的存在来确定正确的types。
例
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; } }
JsonCreationConverter<T>
的上述解决scheme遍布互联网,但在less数情况下存在缺陷。 在ReadJson方法中创build的新的JsonReader不会inheritance任何原始读者的configuration值(Culture,DateParseHandling,DateTimeZoneHandling,FloatParseHandling等等)。 在serializer.Populate()中使用新的JsonReader之前,应该复制这些值。
这是我能想到解决上述问题的最好方法,但我仍然认为有些事情被忽视了:
更新我更新了这个有一个更明确的方法,使现有的读者的副本。 这只是封装了个别JsonReader设置的复制过程。 理想情况下,这个函数会在Newtonsoft库本身中维护,但现在可以使用下面的代码:
/// <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; }
这应该使用如下:
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; }
较老的解决scheme如下:
/// <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); } }
只是以为我会分享一个解决scheme也基于这个工作与Knowntype属性使用reflection,必须从任何基类派生类,解决scheme可以受益于recursionfind最好的匹配类,虽然我不需要它在我的情况下,匹配是通过给转换器的types来完成的,如果它具有KnownTypes,它将扫描它们全部,直到匹配具有jsonstring内所有属性的types,匹配的第一个匹配将被select。
用法如下简单:
string json = "{ Name:\"Something\", LastName:\"Otherthing\" }"; var ret = JsonConvert.DeserializeObject<A>(json, new KnownTypeConverter());
在上面的情况ret将是B型。
JSON类:
[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 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(); } }
这是图腾的答案扩展。 它基本上是一样的东西,但属性匹配是基于序列化的json对象,不反映.net对象。 如果你正在使用[JsonProperty],使用CamelCasePropertyNamesContractResolver,或者做其他任何会导致json不匹配.net对象的东西,这一点很重要。
用法很简单:
[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; } }
作为Totem已知types解决scheme的另一个变体,您可以使用reflection来创build通用typesparsing器,以避免使用已知types属性。
这使用了与Juval Lowy的GenericResolver for WCF类似的技术。
只要你的基类是抽象的或者是一个接口,已知的types将被自动确定,而不必用已知的types属性来修饰。
在我自己的情况下,我select使用$ type属性来指定我的json对象中的types,而不是尝试从属性中确定它,尽pipe您可以从这里借用其他解决scheme来使用基于属性的确定。
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());
这是避免使用jObject.CreateReader()
的另一个解决scheme,而是创build一个新的JsonTextReader
(这是默认的JsonCreate.Deserialze
方法使用的行为:
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); } }
JsonSubTypes项目实现了一个通用的转换器,通过属性的帮助来处理这个特性。
这里提供的具体样本是如何工作的:
[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); }