如何在Json.NET中为二叉树节点序列化添加前后自定义代码?
嘿,我明白你遇到的问题了——在自定义JsonConverter的WriteJson方法里调用serializer.Serialize,结果触发了无限递归,对吧?这其实是个很典型的陷阱,因为你已经把NodeConverter添加到序列化器的转换器列表里了,每次调用serializer.Serialize处理Node类型时,都会优先用你的转换器,于是就循环调用WriteJson没完没了。
不过别担心,完全可以实现“每个Node序列化前后执行自定义代码”的需求,下面给你两种靠谱的方案,看你更倾向哪种:
方案一:手动序列化节点属性(推荐,精准控制每个节点)
这个方案最适合你的需求,因为它能让**每个Node(包括根节点和所有子节点)**都执行前后的自定义代码,同时避免无限递归。核心思路是:不要直接序列化整个Node对象,而是手动逐个序列化它的Data、Left、Right属性,这样既不会触发递归调用转换器,又能让子节点的序列化自然触发转换器的逻辑。
首先,先把你的Node类的属性改成公开的(不然默认序列化访问不到私有成员):
public class Node { public Node Left { get; set; } public Node Right { get; set; } public int Data { get; set; } }
然后修改你的自定义转换器:
public class NodeConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(Node); } 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) { var node = value as Node; if (node == null) { writer.WriteNull(); return; } // 每个Node序列化前的自定义逻辑 Console.WriteLine($"Before serializing Node with Data: {node.Data}"); // 手动构建Node的JSON结构 writer.WriteStartObject(); // 序列化Data(int类型不会触发转换器) writer.WritePropertyName("Data"); serializer.Serialize(writer, node.Data); // 序列化Left节点——这里会触发当前转换器,递归执行Before/After逻辑 writer.WritePropertyName("Left"); serializer.Serialize(writer, node.Left); // 序列化Right节点,同理递归处理 writer.WritePropertyName("Right"); serializer.Serialize(writer, node.Right); writer.WriteEndObject(); // 每个Node序列化后的自定义逻辑 Console.WriteLine($"After serializing Node with Data: {node.Data}"); } }
这样运行你的代码后,每个节点都会输出对应的Before/After日志,同时生成正确的二叉树JSON结构,完全不会有无限递归的问题。
方案二:临时移除转换器(适合仅根节点执行前后逻辑)
如果你只需要在整个树的根节点序列化前后执行自定义代码,而子节点不需要,那可以用这个方案:临时从序列化器的转换器列表中移除当前的NodeConverter,序列化完成后再加回来。
修改后的WriteJson方法:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Console.WriteLine("Before serializing root node"); // 临时移除当前转换器 var currentConverter = serializer.Converters.FirstOrDefault(c => c is NodeConverter); if (currentConverter != null) { serializer.Converters.Remove(currentConverter); // 此时序列化Node会用默认逻辑,不会触发转换器 serializer.Serialize(writer, value); // 把转换器加回去,不影响后续其他序列化操作 serializer.Converters.Add(currentConverter); } else { // 理论上不会走到这里,保险起见直接序列化 serializer.Serialize(writer, value); } Console.WriteLine("After serializing root node"); }
这个方案要注意:如果是多线程环境下使用同一个序列化器,临时移除/添加转换器可能会有线程安全问题,单线程场景下完全没问题。
最后,你的使用代码可以保持不变,只要确保CreateTree方法返回的是正确构建的二叉树就行。
内容的提问来源于stack exchange,提问作者Shmoopy

