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

Newtonsoft Json无法反序列化继承类型(含@odata.type元数据)

问题描述

我已经搜索过相关问题,但没找到可行方案。我有一个包含类型元数据"@odata.type": "#SomeType"的JSON文件,其中SomeType继承自SomeTypeBase,但反序列化后仅得到基类对象。我尝试设置TypeNameHandling.All但无效,是不是@odata.type导致的问题?注:这是一个.NET 7项目。

更新
相关代码如下:

public static T Deserialize<T>(string serialized)
{
    try
    {
        using var reader = new StringReader(serialized);
        using var reader2 = new JsonTextReader(reader)
        {
            DateParseHandling = DateParseHandling.DateTimeOffset,
            FloatParseHandling = FloatParseHandling.Decimal,
        };
        return Serializer.Deserialize<T>(reader2);
    }
    catch (Exception ex)
    {
        throw new JsonSerializationException(ex.Message, ex);
    }
}

Serializer为静态实例:

public static readonly JsonSerializer Serializer = new()
{
    TypeNameHandling = TypeNameHandling.All,
    NullValueHandling = NullValueHandling.Ignore,
    DateParseHandling = DateParseHandling.DateTimeOffset,
    FloatParseHandling = FloatParseHandling.Decimal,
};
解决方案

问题确实出在@odata.type字段上——Newtonsoft.Json默认只会识别$type字段来完成多态反序列化,不会处理@odata.type。要解决这个问题,需要自定义IContractResolver替换默认的类型识别逻辑,让它读取@odata.type的值并映射到对应的实际类型。

步骤1:实现自定义ContractResolver

创建继承自DefaultContractResolver的类,重写相关方法修改类型识别字段,并处理@odata.type中的#前缀:

public class ODataTypeNameContractResolver : DefaultContractResolver
{
    protected override JsonArrayContract CreateArrayContract(Type objectType)
    {
        var contract = base.CreateArrayContract(objectType);
        contract.ItemTypeNameHandling = TypeNameHandling.Auto;
        contract.ItemTypeNamePropertyName = "@odata.type";
        return contract;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);
        contract.TypeNameHandling = TypeNameHandling.Auto;
        contract.TypeNamePropertyName = "@odata.type";
        return contract;
    }

    public override Type ResolveType(string typeName)
    {
        if (string.IsNullOrEmpty(typeName))
            return base.ResolveType(typeName);
        
        // 移除@odata.type值中的#前缀
        var cleanedTypeName = typeName.TrimStart('#');
        // 替换为你的类型命名空间和程序集名称
        var fullTypeName = $"YourNamespace.{cleanedTypeName}, YourAssemblyName";
        return Type.GetType(fullTypeName) ?? base.ResolveType(typeName);
    }
}

注意:需将YourNamespace和YourAssemblyName替换为实际的类型命名空间与程序集名称,若类型在当前程序集内,程序集名称可省略。

步骤2:配置Serializer使用自定义ContractResolver

修改静态Serializer的初始化代码,添加自定义解析器:

public static readonly JsonSerializer Serializer = new()
{
    TypeNameHandling = TypeNameHandling.All,
    NullValueHandling = NullValueHandling.Ignore,
    DateParseHandling = DateParseHandling.DateTimeOffset,
    FloatParseHandling = FloatParseHandling.Decimal,
    ContractResolver = new ODataTypeNameContractResolver() // 添加此行
};

步骤3:验证效果

假设基类与子类定义如下:

namespace YourNamespace
{
    public class SomeTypeBase
    {
        public string BaseProperty { get; set; }
    }

    public class SomeType : SomeTypeBase
    {
        public string DerivedProperty { get; set; }
    }
}

对应的JSON内容:

{
    "@odata.type": "#SomeType",
    "BaseProperty": "基础属性",
    "DerivedProperty": "派生属性"
}

调用Deserialize<SomeTypeBase>(json)后,得到的对象会是SomeType实例,可正常访问DerivedProperty。

补充说明
  • 若类型分布在不同程序集,需在ResolveType方法中正确拼接完整类型名称(包含程序集信息),确保Type.GetType能定位到目标类型。
  • 若@odata.type的值已包含命名空间(如#YourNamespace.SomeType),可直接使用cleanedTypeName作为完整类型名,无需额外拼接。

内容的提问来源于stack exchange,提问作者milandjukic88

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 17:11:18