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

如何用System.Text.Json序列化含多重循环依赖的C#复杂数据结构?

解决System.Text.Json循环依赖与引用保留问题

方案一:使用System.Text.Json内置的ReferenceHandler

你之前对ReferenceHandler的认知存在偏差,它完全可以解决循环依赖和重复引用问题。通过配置ReferenceHandler.Preserve,序列化器会自动生成$id和$ref标记跟踪对象引用,反序列化时能精准还原原始对象关系,且无需修改现有数据模型。

代码示例

// 序列化配置
var options = new JsonSerializerOptions
{
    ReferenceHandler = ReferenceHandler.Preserve,
    WriteIndented = true // 可选,格式化输出方便查看结构
};

// 构造测试对象
var a1 = new TypeA { Id = 1, Name = "A1" };
var b1 = new TypeB { Id = 1, Description = "B1", LinkedA = a1 };
a1.LinkedB = b1;

var c1 = new TypeC { Id = 1, Note = "C1", Reference1 = b1, Reference2 = b1 };

var objToSerialize = new ObjectToSerialize
{
    Data = new List<TypeA> { a1 },
    links = new List<TypeC> { c1 }
};

// 序列化
string json = JsonSerializer.Serialize(objToSerialize, options);

// 反序列化
var deserializedObj = JsonSerializer.Deserialize<ObjectToSerialize>(json, options);

// 验证引用一致性:deserializedObj.Data[0].LinkedB 与 deserializedObj.links[0].Reference1 应为同一实例
bool referencesMatch = ReferenceEquals(deserializedObj.Data[0].LinkedB, deserializedObj.links[0].Reference1);

这个方案完全贴合System.Text.Json原生逻辑,是最省心的选择。

方案二:自定义转换器(按需定制)

如果需要更灵活的引用处理逻辑(比如用业务ID代替自动生成的$id),可以编写自定义转换器。

实现思路

  1. 复用模型中已有的Id作为唯一标识
  2. 序列化时,引用对象仅输出其Id而非完整对象
  3. 反序列化时,先收集所有对象,再根据Id关联引用关系

简化示例(以TypeA转换器为例)

public class TypeAConverter : JsonConverter<TypeA>
{
    private readonly Dictionary<int, TypeA> _typeACache = new();

    public override TypeA Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // 先读取临时对象获取ID
        var temp = JsonSerializer.Deserialize<TypeA>(ref reader, options);
        if (_typeACache.TryGetValue(temp.Id, out var existingInstance))
        {
            return existingInstance;
        }
        _typeACache[temp.Id] = temp;
        // 需配合TypeB的转换器完成引用关联,此处仅作演示
        return temp;
    }

    public override void Write(Utf8JsonWriter writer, TypeA value, JsonSerializerOptions options)
    {
        // 序列化核心属性+关联对象的ID
        writer.WriteStartObject();
        writer.WriteNumber("Id", value.Id);
        writer.WriteString("Name", value.Name);
        if (value.LinkedB != null)
        {
            writer.WriteNumber("LinkedBId", value.LinkedB.Id);
        }
        writer.WriteEndObject();
    }
}

注意:该方案需同步实现TypeB和TypeC的转换器,逻辑相对复杂,仅适合有特殊格式需求的场景。

方案三:更换为Newtonsoft.Json(便捷替代)

如果不想在System.Text.Json上耗费精力,Newtonsoft.Json(Json.NET)对循环引用和引用保留的支持更成熟,配置也更简单。

代码示例

using Newtonsoft.Json;

// 序列化配置
var settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented
};

// 序列化
string json = JsonConvert.SerializeObject(objToSerialize, settings);

// 反序列化
var deserializedObj = JsonConvert.DeserializeObject<ObjectToSerialize>(json, settings);

该方案无需修改模型,仅需引入第三方NuGet包即可解决所有引用问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:11:11