如何用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),可以编写自定义转换器。
实现思路
- 复用模型中已有的
Id作为唯一标识 - 序列化时,引用对象仅输出其
Id而非完整对象 - 反序列化时,先收集所有对象,再根据
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
相关产品推荐
相关产品推荐

