如何使用JSON.NET实现广度优先的引用序列化
解决JSON.NET深度优先序列化导致层级过深的问题
针对你遇到的Puzzle对象循环引用序列化层级过深的问题,提供以下几种可行方案:
方案一:给实体类的引用属性标记优先序列化引用
直接在Edge、Square、Vertex类的交叉引用属性上添加[JsonProperty(IsReference = true)],强制这些属性在序列化时优先使用引用格式,而非嵌套序列化:
public class Edge { [JsonProperty(IsReference = true)] public Vertex VertexA { get; set; } [JsonProperty(IsReference = true)] public Vertex VertexB { get; set; } [JsonProperty(IsReference = true)] public Square AdjacentSquare { get; set; } } public class Square { [JsonProperty(IsReference = true)] public List<Edge> Edges { get; set; } [JsonProperty(IsReference = true)] public List<Vertex> Vertices { get; set; } } public class Vertex { [JsonProperty(IsReference = true)] public List<Edge> ConnectedEdges { get; set; } [JsonProperty(IsReference = true)] public List<Square> AdjacentSquares { get; set; } }
修改后,无论Puzzle的哪个属性先被序列化,交叉引用的对象都会以$ref形式指向已序列化的实例,避免深层嵌套。
方案二:自定义ContractResolver批量处理引用属性
如果不想逐个标记属性,可以自定义契约解析器,自动将所有Edge、Square、Vertex类型的属性(包括单个实例和集合)设置为引用序列化:
public class ReferenceFirstContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); var targetType = property.PropertyType; // 处理单个实体实例 var entityTypes = new[] { typeof(Vertex), typeof(Edge), typeof(Square) }; if (entityTypes.Contains(targetType)) { property.IsReference = true; return property; } // 处理实体集合 if (targetType.IsGenericType && typeof(IEnumerable<>).IsAssignableFrom(targetType.GetGenericTypeDefinition())) { var elementType = targetType.GetGenericArguments()[0]; if (entityTypes.Contains(elementType)) { property.IsReference = true; } } return property; } }
然后更新序列化器配置,添加这个契约解析器:
private JsonSerializer _serializer = new JsonSerializer() { Formatting = Formatting.Indented, PreserveReferencesHandling = PreserveReferencesHandling.All, ReferenceLoopHandling = ReferenceLoopHandling.Serialize, Converters = { new ArrayReferencePreservingConverter()}, TypeNameHandling = TypeNameHandling.Auto, ContractResolver = new ReferenceFirstContractResolver() };
方案三:调整属性序列化顺序+统一实体集合
如果希望所有实体对象都出现在Puzzle的下一级,可以调整Puzzle类的属性序列化顺序,让实体集合优先序列化,确保所有实例先被分配$id,后续引用全部用$ref:
public class Puzzle { // 让实体集合优先序列化 [JsonProperty(Order = -3)] public List<Vertex> Vertices { get; set; } [JsonProperty(Order = -2)] public List<Edge> Edges { get; set; } [JsonProperty(Order = -1)] public List<Square> Squares { get; set; } // 值类型属性放在后面 public int Size { get; set; } }
结合方案一或方案二的配置,序列化后所有实体都会在Puzzle的顶层属性中,交叉引用全部为$ref,JSON层级会大幅降低。
内容的提问来源于stack exchange,提问作者Kaito Kid
相关产品推荐
相关产品推荐

