C#中如何使用Json.NET反序列化带外部系统引用ID的数组?
问题核心成因
你遇到的Rooms属性为null的问题,本质是当前使用场景和Json.NET内置PreserveReferencesHandling的设计逻辑完全不匹配,并非配置写错:
- 内置引用处理的工作规则:仅处理Json.NET自身序列化流程生成的$id/$ref元数据,反序列化时按照JSON遍历顺序维护「$id值-对象实例」的内部映射表,遇到$ref字段时如果映射表无对应条目,就会静默跳过赋值
- 结构不匹配的具体表现:
- 你的JSON中被引用的Room对象全部定义在根级
rooms数组中,位置在Tour的rooms数组之后,遍历到Tour里的$ref时,映射表还没收录后面的$id条目,自然无法完成回填 - 所有$id值都是外部系统生成的业务标识,并非Json.NET序列化阶段自动分配的ID,内置映射表根本不会主动收录这些条目,也不会把外部$id自动赋值到模型的
Id属性上
- 你的JSON中被引用的Room对象全部定义在根级
适配你当前场景的可行方案
方案1:自定义两阶段解析(最高稳定性,无需调整外部JSON结构)
完全脱离内置引用处理机制,分两步完成关联,最适配外部系统生成的JSON格式:
- 先反序列化读取全量原始结构,根据根级
rooms数组的$id值构建Room实例的字典映射 - 遍历每个Tour对象,从
rooms数组的$ref值匹配字典中的对应Room实例,手动赋值
对应实现代码:
// 定义临时中间模型,承接原始JSON结构,避免被内置元数据字段干扰 public class RawRootModel { public List<RawTour> Tours { get; set; } public List<RawRoom> Rooms { get; set; } } public class RawTour { public string Name { get; set; } public List<RoomRefItem> Rooms { get; set; } } public class RoomRefItem { [JsonProperty("$ref")] public string RefId { get; set; } } public class RawRoom { [JsonProperty("$id")] public string ExternalId { get; set; } public string Name { get; set; } }
// 基础配置:关闭内置引用处理,跳过元数据字段干扰 var baseSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.None, MetadataPropertyHandling = MetadataPropertyHandling.Ignore }; // 第一阶段:读取原始JSON结构 var rawRoot = JsonConvert.DeserializeObject<RawRootModel>(json, baseSettings); // 构建Room实例的ID映射字典 var roomDict = rawRoot.Rooms.Select(room => new Room { Id = room.ExternalId, Name = room.Name }).ToDictionary(room => room.Id); // 第二阶段:为Tour关联匹配的Room对象 var finalTours = rawRoot.Tours.Select(tour => new Tour { Name = tour.Name, Rooms = tour.Rooms.Select(refItem => roomDict[refItem.RefId]).ToList() }).ToList(); // 组装最终业务模型 var finalData = new Data { Tours = finalTours, Rooms = roomDict.Values.ToList() };
这个方案完全兼容外部系统生成的ID格式,不会出现内置机制导致的属性丢失问题。
方案2:调整结构匹配内置规则(仅当可修改外部JSON输出时可选)
如果可以让外部系统把rooms数组调整到tours数组之前,可以配合开启MetadataPropertyHandling.ReadAhead配置,让Json.NET提前全量扫描JSON、把所有$id条目先录入内部映射表,再开始业务对象反序列化:
var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead };
注意:该方案依然无法自动把外部$id值赋值到模型的Id属性上,因为内置机制会把$id当成专属元数据处理,不会映射为业务属性,最终Room对象的Id字段依然会为空,需要额外补充映射逻辑。
核心疑问明确解答
- 不存在配置层面的低级错误,内置
PreserveReferencesHandling的设计目标是解决Json.NET自身序列化产生的循环引用、重复对象引用问题,不支持解析跨层级分布、外部自定义生成的$id/$ref结构 - Json.NET没有开箱即用的能力适配你当前的JSON结构
- 反序列化完成后按照ID手动匹配关联是这类场景的通用处理方式,稳定性远高于硬套内置引用配置的方案
内容的提问来源于stack exchange,提问作者cyx
相关产品推荐
相关产品推荐

