You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取前能否获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 21:45:35