基于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(); } }
使用说明
- 在反序列化前,确保
DefConverter已注册到JsonSerializerSettings中。 - 完成所有JSON的反序列化后,调用
DefReferenceResolver.ResolvePendingReferences()完成引用替换。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

