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

基于JsonConverter<T>实现JSON版本迁移器遇递归及实例化问题

解决Json.NET JsonConverter迁移时的无限递归与无参构造问题

核心思路

你的迁移逻辑方向是对的:先修正JSON结构,再反序列化到目标类型。当前遇到的两个问题可以通过以下方式解决:

  1. 直接调用传入的serializer.Deserialize会触发当前转换器,导致无限递归——需要临时禁用当前转换器来绕开递归
  2. 目标类型无无参构造,不想用反射——利用Json.NET原生的契约解析能力来处理实例创建,无需手动反射

解决无限递归:临时排除当前转换器

在JsonMigrator<T>的ReadJson方法中,不要直接使用传入的serializer进行反序列化,而是创建一个移除了当前转换器的新JsonSerializer实例,这样反序列化时就不会再次触发当前转换器,避免递归。

复用Json.NET原生实例创建能力

Json.NET本身可以通过ContractResolver解析目标类型的构造逻辑(哪怕没有无参构造,比如私有构造、带参数的构造),只要用排除当前转换器的新Serializer来反序列化,就能让Json.NET自动处理实例创建,不需要手动调用Activator.CreateInstance。

代码实现示例

抽象迁移转换器基类

public abstract class JsonMigrator<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // 1. 读取原始JSON到JObject,方便修改结构
        var jObject = JObject.Load(reader);

        // 2. 调用抽象方法执行具体的JSON迁移逻辑(子类实现)
        MigrateJson(jObject);

        // 3. 创建排除当前转换器的新Serializer,避免递归
        var newSerializer = new JsonSerializer();
        // 复制原Serializer的配置,保证行为一致
        newSerializer.ContractResolver = serializer.ContractResolver;
        newSerializer.DateFormatHandling = serializer.DateFormatHandling;
        newSerializer.NullValueHandling = serializer.NullValueHandling;
        // 只添加除当前转换器外的其他转换器
        newSerializer.Converters.AddRange(serializer.Converters.Where(c => c.GetType() != this.GetType()));

        // 4. 让Json.NET自动处理实例创建并反序列化
        return jObject.ToObject<T>(newSerializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // 序列化无需迁移的话,直接用默认逻辑即可
        serializer.Serialize(writer, value);
    }

    // 抽象方法:子类实现具体的JSON字段迁移、结构修改逻辑
    protected abstract void MigrateJson(JObject jObject);
}

子类迁移实现示例(以User类为例)

public class UserMigrator : JsonMigrator<User>
{
    protected override void MigrateJson(JObject jObject)
    {
        // 示例1:旧版本用"UserName"字段,新版本用"Name",迁移时替换
        if (jObject.ContainsKey("UserName"))
        {
            jObject["Name"] = jObject["UserName"];
            jObject.Remove("UserName");
        }

        // 示例2:给缺失的字段设置默认值
        if (!jObject.ContainsKey("CreatedAt"))
        {
            jObject["CreatedAt"] = DateTime.UtcNow;
        }
    }
}

使用方式

注册转换器后正常反序列化即可:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new UserMigrator());

var oldJson = "{\"UserName\":\"Alice\",\"Age\":30}";
var user = JsonConvert.DeserializeObject<User>(oldJson, settings);
// 最终user.Name为"Alice",CreatedAt会被自动填充

关键细节说明

  • 避免递归的核心:新创建的JsonSerializer排除了当前转换器,所以jObject.ToObject<T>不会再次触发JsonMigrator<T>的ReadJson方法
  • 配置一致性:复制原Serializer的契约解析器、日期格式等配置,保证反序列化行为和原有逻辑完全一致
  • 原生实例创建:JObject.ToObject<T>依赖Json.NET的契约解析逻辑,自动适配目标类型的构造方式,比反射更稳健,完全贴合Json.NET的原生行为

内容的提问来源于stack exchange,提问作者Smolakian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 03:50:20