C# 从 API 反序列化抽象数组
C# 从 API 反序列化抽象数组
我正在尝试扩展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; } } Listpeople = new List { new Employee(), new Employee(), new Artist(), };
我如何将以下Json反序列化为List
[
{
"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实现来处理此问题。关于这个的文档和示例在网络上非常稀少。我似乎无法正确使用重写的ReadJson()方法实现JsonConverter。
在使用标准的CustomCreationConverter时,我在生成正确类型(Person或Employee)方面遇到了困难,因为为了确定这一点,需要分析JSON,而使用Create方法无法进行内置的方式来做到这一点。
我发现了一个关于类型转换的讨论线程,结果发现提供了答案。这是一个链接:Type converting (archived link)。
所需的是子类化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\" }]"; Listpersons = JsonConvert.DeserializeObject >(json, new PersonConverter());
public class PersonConverter : JsonCreationConverter{ 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 : JsonConverter { /// /// Create an instance of objectType, based properties in the JSON object /// /// type of object expected /// /// contents of JSON object that will be deserialized /// ///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; } }
这个方案在互联网上到处都有,但是在某些情况下会出现问题。在ReadJson方法中创建的新JsonReader没有继承任何原始读取器的配置值(Culture,DateParseHandling,DateTimeZoneHandling,FloatParseHandling等)。在使用新的JsonReader之前,应该将这些值复制过去。
如果不想创建新的JsonReader(由于原因),或者需要根据父对象的某些值来决定所创建对象的类型,请参考这个解决方案:stackoverflow.com/a/22539730/1038496。对于这种类型的问题,这个解决方案对我来说更有效和更清晰。
如果目标类型没有无参数的构造函数,这可能有点麻烦。你必须在Create方法中做更多的重活。默认的JsonConverter对这些没有问题。使用serializer.Deserialize而不是Populate将起作用,但是如果在使用之前不从设置中删除自定义转换器,则会导致无限循环,但这对于复合对象(例如一个持有多个Person对象的Party)会带来问题。有什么想法吗?
如何处理自定义转换器中的引用?
- 非常感谢这个评论。我没有注意到JsonConverter基类中的CanWrite默认值,但通过重写并返回False,我现在得到了我想要的,即一个使用标准代码进行写入,但在读取时扩展的转换器。非常棒,再次感谢!
我发现如果不处理NULL情况,将会发生错误。使用:if (reader.TokenType == JsonToken.Null) return null; 参考链接:stackoverflow.com/a/34185296/857291
我真的很想看到这个解决方案扩展到处理序列化和反序列化,并作为JSON.NET的示例。
问题的出现原因:
该问题的出现原因是在使用C#进行反序列化时,API返回的JSON数据中包含一个抽象数组。在这种情况下,C#默认的反序列化方法无法正确识别和实例化抽象类型的数组,导致反序列化失败。
解决方法:
为了解决这个问题,可以使用反射和KnownType属性来实现一个自定义的JSON转换器。该转换器会扫描KnownType属性中的所有派生类,然后根据JSON数据中的属性匹配来选择最匹配的派生类进行反序列化。
具体的解决方法如下:
1. 首先,定义一个JSON转换器类KnownTypeConverter,该类继承自JsonConverter类,并重写其中的CanConvert、ReadJson和WriteJson方法。
2. 在CanConvert方法中,判断传入的objectType是否有KnownType属性,如果有,则返回true,表示该类型可以被转换。
3. 在ReadJson方法中,首先从JSON数据流中加载JObject对象。然后使用反射获取objectType上的KnownType属性,并遍历所有派生类。
4. 对于每个派生类,获取其所有属性,并与JSON数据中的属性进行匹配。如果所有属性都匹配,则创建该派生类的实例,并使用JsonSerializer.Populate方法将JSON数据填充到该实例中。
5. 最后,返回反序列化后的对象。
6. 如果没有找到匹配的派生类,则抛出ObjectNotFoundException异常。
7. WriteJson方法暂时不实现,可以抛出NotImplementedException异常。
这个解决方法可以很好地处理抽象数组的反序列化问题,并且可以通过KnownType属性来选择最佳匹配的派生类进行反序列化。
然而,需要注意的是,如果有多个派生类具有完全相同的属性名称,可能会导致匹配错误的问题。在这种情况下,可能需要进一步优化转换器的逻辑来确保选择正确的派生类进行反序列化。
C# Deserializing abstract array from API问题的出现原因是在上述的解决方案中,ReadJson方法中创建的新的JsonReader没有继承任何原始读取器的配置值(Culture,DateParseHandling,DateTimeZoneHandling,FloatParseHandling等),在使用新的JsonReader进行serializer.Populate()之前应该将这些值复制过来。
解决这个问题的方法是创建一个新的reader来复制配置值。可以使用以下方法:
///Creates a new reader for the specified jObject by copying the settings /// from an existing reader. /// The reader whose settings should be copied. /// The jToken to create a new reader for. ///The new disposable reader. 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; }
在ReadJson方法中使用该方法:
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; }
原来的解决方案如下:
///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. public abstract class JsonCreationConverter: JsonConverter { /// Create an instance of objectType, based properties in the JSON object /// type of object expected /// contents of JSON object that will be deserialized protected abstract T Create(Type objectType, JObject jObject); ///Determines if this converted is designed to deserialization to objects of the specified type. /// The target type for deserialization. ///True if the type is supported. public override bool CanConvert(Type objectType) { // FrameWork 4.5 // return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); // Otherwise return typeof(T).IsAssignableFrom(objectType); } ///Parses the json to the specified type. /// Newtonsoft.Json.JsonReader /// Target type. /// Ignored /// Newtonsoft.Json.JsonSerializer to use. ///Deserialized Object 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; } ///Serializes to the specified type /// Newtonsoft.Json.JsonWriter /// Object to serialize. /// Newtonsoft.Json.JsonSerializer to use. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }
需要注意的是,不要忘记实现CanWrite方法,并设置为false,以避免出现自引用循环。还需要实现WriteJson方法,以指定如何将对象转换为Json格式。