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

基于JsonReader深度条件使用自定义转换器的反序列化循环问题

解决Json.NET自定义转换器深度判断时的无限循环问题

问题核心是:当在自定义转换器的ReadJson方法中调用serializer.Deserialize时,因当前转换器已注册到序列化器中,会再次触发自身导致无限递归。要实现根节点Def正常反序列化、内部Def作为引用的需求,需绕开自定义转换器处理根节点的默认反序列化,同时保留非根节点的引用逻辑。

方案一:临时移除转换器执行默认反序列化

创建临时的JsonSerializerSettings,复制原序列化器的关键配置但移除当前的DefConverter,用这个临时设置完成根节点的默认反序列化。

public class DefConverter : JsonConverter {
    public override bool CanConvert(Type objectType) {
        return objectType == typeof(Def);
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
        if (reader.Depth == 0) {
            // 复制原序列化器的核心配置
            var tempSettings = new JsonSerializerSettings {
                ContractResolver = serializer.ContractResolver,
                NullValueHandling = serializer.NullValueHandling,
                // 按需复制其他配置,比如DateFormatHandling、ReferenceLoopHandling等
            };
            // 排除当前转换器,避免递归调用
            tempSettings.Converters.AddRange(serializer.Converters.Where(c => c.GetType() != typeof(DefConverter)));
            
            // 使用临时配置执行默认反序列化
            var rootDef = JsonConvert.DeserializeObject(reader, objectType, tempSettings) as Def;
            // 将根节点Def存入缓存,供后续引用解析使用
            DefReferenceResolver.RegisterDef(rootDef);
            return rootDef;
        }
        
        // 处理非根节点的Def:读取ID创建骨架,加入待解析列表
        var jObj = JObject.Load(reader);
        var defId = jObj["id"]?.ToString();
        if (string.IsNullOrEmpty(defId)) return null;

        // 优先从缓存获取已存在的Def,避免重复创建
        if (DefReferenceResolver.TryGetDef(defId, out var existingDef)) {
            return existingDef;
        }

        var skeletonDef = new Def { Id = defId };
        DefReferenceResolver.AddPendingDef(skeletonDef);
        return skeletonDef;
    }
    
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
        throw new NotImplementedException();
    }
}

方案二:通过JToken手动解析根节点

直接将根节点读取为JToken,再用ToObject方法执行默认反序列化(该方法默认不使用当前注册的自定义转换器)。

public class DefConverter : JsonConverter {
    public override bool CanConvert(Type objectType) {
        return objectType == typeof(Def);
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
        if (reader.Depth == 0) {
            // 读取根节点为JToken
            var rootToken = JToken.Load(reader);
            // 执行默认反序列化,不会触发当前转换器
            var rootDef = rootToken.ToObject<Def>();
            DefReferenceResolver.RegisterDef(rootDef);
            return rootDef;
        }
        
        // 非根节点处理逻辑与方案一一致
        var jObj = JObject.Load(reader);
        var defId = jObj["id"]?.ToString();
        if (string.IsNullOrEmpty(defId)) return null;

        if (DefReferenceResolver.TryGetDef(defId, out var existingDef)) {
            return existingDef;
        }

        var skeletonDef = new Def { Id = defId };
        DefReferenceResolver.AddPendingDef(skeletonDef);
        return skeletonDef;
    }
    
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
        throw new NotImplementedException();
    }
}

引用解析辅助类

需要一个静态类管理已注册的Def和待解析的骨架,在所有反序列化完成后统一处理引用替换:

public static class DefReferenceResolver {
    private static readonly Dictionary<string, Def> _registeredDefs = new();
    private static readonly List<Def> _pendingDefs = new();

    public static void RegisterDef(Def? def) {
        if (def == null || string.IsNullOrEmpty(def.Id)) return;
        if (!_registeredDefs.ContainsKey(def.Id)) {
            _registeredDefs[def.Id] = def;
        }
    }

    public static bool TryGetDef(string id, out Def? def) {
        return _registeredDefs.TryGetValue(id, out def);
    }

    public static void AddPendingDef(Def def) {
        _pendingDefs.Add(def);
    }

    // 所有反序列化完成后调用此方法,替换骨架的引用为实际Def实例
    public static void ResolvePendingReferences() {
        foreach (var pendingDef in _pendingDefs) {
            if (_registeredDefs.TryGetValue(pendingDef.Id, out var actualDef)) {
                // 根据Def的实际结构替换引用属性,示例:
                // pendingDef.RelatedDef = actualDef;
                // pendingDef.ParentDef = actualDef;
            }
        }
        _pendingDefs.Clear();
    }
}

使用说明

  1. 在反序列化前,确保DefConverter已注册到JsonSerializerSettings中。
  2. 完成所有JSON的反序列化后,调用DefReferenceResolver.ResolvePendingReferences()完成引用替换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 00:50:23