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

如何从无引用信息的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:34:56