JSON.NET 保留对象引用序列化后反序列化引用丢失如何解决?
问题根因
这是Newtonsoft.Json(Json.NET)处理计算属性+保留引用逻辑的已知行为,算不上严格意义的Bug,但确实不符合预期:
- 你的
AllContexts是返回非泛型IEnumerable的只读计算属性,序列化时会正常输出内容,但反序列化时Json.NET无法识别集合元素的实际类型为Context,只会反序列化为通用JObject存入引用缓存 - 后续反序列化
Steps.Contexts时,通过$ref查询到的缓存对象是JObject,无法转换为Context类型,最终得到null值 - 调整属性序列化顺序为
Steps在前时,Context实例会先被正确反序列化存入缓存,后续AllContexts的引用可以正常命中,但不符合你要求的JSON结构
可行绕过方案(满足输出JSON结构要求,同时反序列化正常)
方案1:修改属性定义+序列化/反序列化回调(无侵入,最简单)
调整Plan类的定义,让AllContexts参与序列化,反序列化后自动修复引用:
class Plan { // 改为泛型IEnumerable<Context>,让Json.NET识别元素类型 [JsonProperty(Order = 1)] public IEnumerable<Context> AllContexts { get; set; } [JsonProperty(Order = 2)] public ICollection<Step> Steps { get; set; } = new List<Step>(); // 序列化前自动计算AllContexts的值,保证JSON输出顺序符合要求 [OnSerializing] internal void OnSerializing(StreamingContext context) { AllContexts = Steps.SelectMany(i => i.Contexts).ToList(); } // 反序列化完成后重新计算AllContexts,和Steps保持同步 [OnDeserialized] internal void OnDeserialized(StreamingContext context) { // 修复Steps.Contexts的null引用(如果存在) var allContexts = AllContexts.ToList(); int index = 0; foreach (var step in Steps) { var contextList = step.Contexts.ToList(); for (int i = 0; i < contextList.Count; i++) { contextList[i] ??= allContexts[index]; index++; } step.Contexts = contextList; } // 重新指向计算结果,避免后续修改Steps时AllContexts不同步 AllContexts = Steps.SelectMany(i => i.Contexts).ToList(); } }
原序列化配置完全不需要修改,生成的JSON结构完全符合你的预期,反序列化后也不会出现null值。
方案2:自定义ContractResolver(不修改实体类)
如果你不想改动现有实体类的定义,可以添加自定义ContractResolver,强制反序列化时跳过AllContexts属性的处理,同时开启引用解析全局配置:
public class IgnoreAllContextsResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); if (property.DeclaringType == typeof(Plan) && property.PropertyName == nameof(Plan.AllContexts)) { // 反序列化时忽略该属性 property.Ignored = true; } return property; } }
反序列化时指定该Resolver即可:
var deserializeSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, ContractResolver = new IgnoreAllContextsResolver(), // 提前扫描所有$id元数据存入缓存,即使属性被忽略也不影响引用解析 MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead }; var deserialized = JsonConvert.DeserializeObject<Plan>(json, deserializeSettings);
该方案不需要修改实体类,序列化时用原配置即可生成符合要求的JSON,反序列化时用额外配置即可正常解析。
内容的提问来源于stack exchange,提问作者Jeff
相关产品推荐
相关产品推荐

