读取前能否获取JsonReader属性?异构IRecord反序列化难题
问题描述
现有基础API类通过以下代码从接口获取并反序列化对象:
HttpResponseMessage GetResponse(string path) => Task.Run(async() => await client.GetAsync(path)); T GetTFromRequest<T>(string path, JsonConverter[] converters) => Task.Run(async() => await GetResponse(path).Content.ReadAsAsync<T>(converters)); // 转换器会被放入新的JsonMediaTypeFormatter
业务API类示例:
public class ExampleAPI : BaseAPI { private static readonly JsonConverter[] converters = new[] { new RecordConverter() }; public string APIKey { get; set; } public ExampleData GetExampleData(/* 参数如 string apiKey */) => GetTFromRequest<ExampleData>($"examplepath?key={APIKey}", converters); }
API返回JSON示例:
{ "success":true, "records":[ { "Foo":"Bar", "Data":"Dahta" } ] }
相关实体定义:
public class RecordConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(IRecord); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } public interface IRecord {} public class ExampleData { public bool success { get; set; } = true; public IRecord[] records { get; set; } = new[] {}; } public class DataRecord : IRecord { public string Foo { get; set; } public string Data { get; set; } } public class FaltyRecord : IRecord { public string Bar { get; set; } public string Dahta { get; set; } }
核心问题:在RecordConverter.ReadJson方法中,需要判断records数组中的对象是DataRecord还是FaltyRecord,但调用reader.Read()会移动指针导致后续反序列化出错。已知XmlReader有GetSubtree()方法可实现不移动原指针的子树读取,也考虑过用JObject处理,但不清楚如何用JsonReader构造JObject,同时想确认是否能在读取前获取JsonReader的属性。
解决方案
方法一:使用JObject.Load读取并重置Reader位置
通过JObject.Load(reader)加载当前Reader指向的JSON对象,之后手动重置Reader到对象起始位置,避免后续反序列化受影响:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 加载当前对象为JObject var jObject = JObject.Load(reader); // 重置Reader到对象起始的StartObject位置 reader.Read(); reader.Depth--; reader.Read(); // 根据属性判断对象类型 if (jObject.ContainsKey("Foo")) { return serializer.Deserialize<DataRecord>(reader); } else if (jObject.ContainsKey("Bar")) { return serializer.Deserialize<FaltyRecord>(reader); } else { throw new InvalidOperationException("无法识别的Record类型"); } }
JObject.Load会读取整个对象直到EndObject,此时Reader指向EndObject的下一个Token,手动重置后可让原Reader回到对象起始点,保证后续反序列化正常进行。
方法二:克隆JsonReader
利用JsonReader的Clone()方法创建独立的副本,操作克隆后的Reader不会影响原Reader的位置,实现更简洁:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 克隆独立Reader用于探测属性 var cloneReader = reader.Clone(); var jObject = JObject.Load(cloneReader); // 根据属性判断类型,使用原Reader完成反序列化 if (jObject.ContainsKey("Foo")) { return serializer.Deserialize<DataRecord>(reader); } else if (jObject.ContainsKey("Bar")) { return serializer.Deserialize<FaltyRecord>(reader); } else { throw new InvalidOperationException("无法识别的Record类型"); } }
关于读取前获取JsonReader属性的问题
可以直接通过reader的内置属性获取当前状态,无需调用Read():
reader.TokenType:当前Token的类型(如StartObject、PropertyName)reader.Value:当前Token的值reader.Depth:当前JSON的嵌套深度reader.HasValue:当前Token是否包含有效值
进入ReadJson方法时,Reader已指向要反序列化对象的StartObject位置,可直接读取这些属性判断状态。
内容的提问来源于stack exchange,提问作者Doshorte Dovencio
相关产品推荐
相关产品推荐

