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

JSON.NET 保留对象引用序列化后反序列化引用丢失如何解决?

问题根因

这是Newtonsoft.Json(Json.NET)处理计算属性+保留引用逻辑的已知行为,算不上严格意义的Bug,但确实不符合预期:

  1. 你的AllContexts是返回非泛型IEnumerable的只读计算属性,序列化时会正常输出内容,但反序列化时Json.NET无法识别集合元素的实际类型为Context,只会反序列化为通用JObject存入引用缓存
  2. 后续反序列化Steps.Contexts时,通过$ref查询到的缓存对象是JObject,无法转换为Context类型,最终得到null值
  3. 调整属性序列化顺序为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 21:27:01