如何从无引用信息的JSON反序列化并重建带反向引用的Tree层级对象?
如何反序列化无引用信息的JSON并重建父子对象引用?
首先,咱们先明确核心需求:你有Tree→Branch→Leaf的层级结构,原来用Newtonsoft.Json的引用序列化生成的JSON带$id/$ref标记,现在想要去掉这些冗余信息,生成干净的层级JSON,同时反序列化时还要正确调用Branch和Leaf带父实例参数的构造函数,自动建好反向引用。
先回顾下你的类结构(格式化后的版本):
public class Tree { public string Name { get; set; } public List<Branch> Nodes { get; set; } = new List<Branch>(); } public class Branch { public Branch(Tree parent) { Parent = parent; } public Tree Parent { get; } public string Name { get; set; } public List<Leaf> Nodes { get; set; } = new List<Leaf>(); } public class Leaf { public Leaf(Branch parent) { Parent = parent; } public Branch Parent { get; } public string Name { get; set; } }
这里的关键点是:Branch和Leaf都没有无参构造函数,而且必须通过构造函数传入父对象,所以默认的反序列化逻辑会直接报错——因为它找不到合适的构造方法,也不知道怎么关联父对象。
解决方案:自定义Json转换器处理父子关联
咱们可以通过Newtonsoft.Json的JsonConverter抽象类,为Branch和Leaf分别编写自定义转换器,在反序列化过程中手动获取父对象、调用正确的构造函数,同时序列化时自动忽略Parent属性,生成干净的JSON。
第一步:编写Leaf转换器
Leaf的转换器需要从序列化上下文里拿到它的父Branch,然后调用构造函数,再填充Name属性:
public class LeafConverter : JsonConverter<Leaf> { public override Leaf ReadJson(JsonReader reader, Type objectType, Leaf existingValue, bool hasExistingValue, JsonSerializer serializer) { // 从上下文获取父Branch实例 var parentBranch = serializer.Context.Context as Branch; if (parentBranch == null) throw new InvalidOperationException("反序列化Leaf时无法找到父Branch,请确保上下文已设置"); // 加载JSON数据到JObject var jLeaf = JObject.Load(reader); // 调用带父参数的构造函数 var leaf = new Leaf(parentBranch); // 填充Name属性(如果有其他属性,在这里继续添加赋值逻辑) leaf.Name = jLeaf["Name"]?.ToString(); return leaf; } public override void WriteJson(JsonWriter writer, Leaf value, JsonSerializer serializer) { // 序列化时只输出Name,忽略Parent(避免循环引用和冗余) var jLeaf = new JObject { ["Name"] = value.Name }; jLeaf.WriteTo(writer); } }
第二步:编写Branch转换器
Branch的转换器逻辑类似,需要获取父Tree,调用构造函数,然后处理Leaf列表的反序列化(这时要把当前Branch作为上下文传给Leaf转换器):
public class BranchConverter : JsonConverter<Branch> { public override Branch ReadJson(JsonReader reader, Type objectType, Branch existingValue, bool hasExistingValue, JsonSerializer serializer) { // 从上下文获取父Tree实例 var parentTree = serializer.Context.Context as Tree; if (parentTree == null) throw new InvalidOperationException("反序列化Branch时无法找到父Tree,请确保上下文已设置"); var jBranch = JObject.Load(reader); // 调用带父参数的构造函数 var branch = new Branch(parentTree); branch.Name = jBranch["Name"]?.ToString(); // 反序列化Leaf列表时,把当前Branch作为上下文传入 var leafSerializer = new JsonSerializer(); leafSerializer.Context = new StreamingContext(StreamingContextStates.All, branch); leafSerializer.Converters.Add(new LeafConverter()); branch.Nodes = jBranch["Nodes"]?.ToObject<List<Leaf>>(leafSerializer) ?? new List<Leaf>(); return branch; } public override void WriteJson(JsonWriter writer, Branch value, JsonSerializer serializer) { // 序列化时只输出Name和Leaf列表,忽略Parent var jBranch = new JObject { ["Name"] = value.Name, ["Nodes"] = JArray.FromObject(value.Nodes, serializer) }; jBranch.WriteTo(writer); } }
第三步:反序列化Tree的方法
最后,我们需要写一个专门的反序列化方法,先创建Tree实例,再把Tree作为上下文传入Branch转换器,完成整个层级的反序列化:
public static Tree DeserializeTree(string cleanJson) { // 先解析JSON到JObject,方便分步处理 var jTree = JObject.Parse(cleanJson); // 创建Tree实例并填充基本属性 var tree = new Tree { Name = jTree["Name"]?.ToString() }; // 反序列化Branch列表,把当前Tree作为上下文传入 var branchSerializer = new JsonSerializer(); branchSerializer.Context = new StreamingContext(StreamingContextStates.All, tree); branchSerializer.Converters.Add(new BranchConverter()); tree.Nodes = jTree["Nodes"]?.ToObject<List<Branch>>(branchSerializer) ?? new List<Branch>(); return tree; }
测试一下
假设你有这样的干净JSON(没有任何引用标记):
{ "Name": "百年橡树", "Nodes": [ { "Name": "主枝干", "Nodes": [ { "Name": "向阳叶子1" }, { "Name": "向阳叶子2" } ] }, { "Name": "侧枝干", "Nodes": [ { "Name": "背阴叶子" } ] } ] }
调用DeserializeTree(jsonString)后,得到的Tree对象里:
- 每个
Branch的Parent都指向这个Tree实例 - 每个
Leaf的Parent都指向对应的Branch实例 - 所有构造函数都正确调用了带父参数的版本
额外说明
- 如果你的类后续添加了其他属性,只需要在对应的转换器里从JObject中读取并赋值即可,扩展性很强。
- 序列化时,如果你用这个转换器生成JSON,会自动忽略
Parent属性,完全没有冗余的引用信息,体积更小也更易读。
内容的提问来源于stack exchange,提问作者Marcel
相关产品推荐
相关产品推荐

