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

替换Newtonsoft ContractResolver遇问题:TypeInfoResolver Modifier解析基类而非实际类型

问题:System.Text.Json TypeInfoResolver Modifier无法正确识别Exception子类的实际类型

在将Newtonsoft.Json的ContractResolver替换为System.Text.Json的TypeInfoResolver Modifier时,遇到核心问题:

  • 当变量声明为基类Exception(实际是ValidationException子类实例)时,Newtonsoft的ContractResolver能正确接收到实际运行时类型,但System.Text.Json的Modifier仅拿到编译时的Exception类型,导致序列化结果不符合预期。
  • 用var声明变量时逻辑正常,但全局异常处理场景下必须处理声明为Exception类型的对象,需解决类型识别问题。

现有实现代码

Newtonsoft.Json的ContractResolver实现

public sealed class BaseExceptionContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    public BaseExceptionContractResolver()
    {
        NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy();
    }

    protected override IList<Newtonsoft.Json.Serialization.JsonProperty> CreateProperties(Type type, Newtonsoft.Json.MemberSerialization memberSerialization)
    {
        Debug.WriteLine($"Type in Newtonsoft.Json: {type}");
        return !type.IsSubclassOf(typeof(BaseException)) ? base.CreateProperties(type, memberSerialization) : base.CreateProperties(typeof(BaseException), memberSerialization).Where(IsExceptionProperty).ToList();
    }

    private bool IsExceptionProperty(Newtonsoft.Json.Serialization.JsonProperty property)
    {
        if (property.PropertyName == null)
        {
            return false;
        }

        return property.PropertyName.Equals(nameof(BaseException.Type), StringComparison.InvariantCultureIgnoreCase)
               || property.PropertyName.Equals(nameof(BaseException.Details), StringComparison.InvariantCultureIgnoreCase)
               || property.PropertyName.Equals(nameof(BaseException.Description), StringComparison.InvariantCultureIgnoreCase);
    }
}

System.Text.Json的Modifier实现

public static class Modifiers
{
    public static void OnlyBaseExceptionProperties(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo)
    {
        Debug.WriteLine($"Type in System.Text.Json: {typeInfo.Type}");

        if (!typeInfo.Type.IsSubclassOf(typeof(BaseException)))
        {
            return;
        }

        foreach (var property in typeInfo.Properties)
        {
            if (property.Name.Equals(nameof(BaseException.Type), StringComparison.InvariantCultureIgnoreCase)
                || property.Name.Equals(nameof(BaseException.Details), StringComparison.InvariantCultureIgnoreCase)
                || property.Name.Equals(nameof(BaseException.Description), StringComparison.InvariantCultureIgnoreCase))
            {
                property.ShouldSerialize = static (_, _) => true;
            }
            else
            {
                property.ShouldSerialize = static (_, _) => false;
            }
        }
    }
}

测试代码与输出

internal class Program
{
    static void Main(string[] args)
    {
        var baseExceptionContractResolver = new BaseExceptionContractResolver();

        var newtonsoftSettings = new Newtonsoft.Json.JsonSerializerSettings
            { Formatting = Newtonsoft.Json.Formatting.Indented, ContractResolver = baseExceptionContractResolver };

        var textJsonSettings = new System.Text.Json.JsonSerializerOptions
        {
            PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
            WriteIndented = true,
            TypeInfoResolver = new DefaultJsonTypeInfoResolver
            {
                Modifiers = { Modifiers.OnlyBaseExceptionProperties }
            }
        };

        Exception exception1 = new ValidationException("Test");
        var exception2 = new ValidationException("Test");

        var s1e1 = Newtonsoft.Json.JsonConvert.SerializeObject(exception1, newtonsoftSettings);
        var s2e1 = System.Text.Json.JsonSerializer.Serialize(exception1, textJsonSettings);

        Console.WriteLine(s1e1 == s2e1);

        var s1e2 = Newtonsoft.Json.JsonConvert.SerializeObject(exception2, newtonsoftSettings);
        var s2e2 = System.Text.Json.JsonSerializer.Serialize(exception2, textJsonSettings);

        Console.WriteLine(s1e2 == s2e2);
            
        Console.ReadLine();
    }
}

输出:

False
True

问题原因

System.Text.Json的DefaultJsonTypeInfoResolver默认基于编译时类型生成JsonTypeInfo,而Newtonsoft.Json会自动检查对象的运行时实际类型并生成对应契约。当传入Exception类型变量时,Resolver默认生成Exception的类型信息,不会自动切换到子类类型。


解决方案

方案1:序列化时强制使用object类型重载

最快速的解决方式是将对象强制转换为object类型,让System.Text.Json自动识别运行时类型:

var s2e1 = System.Text.Json.JsonSerializer.Serialize((object)exception1, textJsonSettings);

该方式无需修改Resolver或Modifier,适合全局异常处理场景快速适配。

方案2:配置多态支持+修改Modifier逻辑

通过配置多态序列化,让Resolver为Exception子类生成正确的类型信息,同时调整Modifier的类型判断逻辑:

// 配置JsonSerializerOptions
var textJsonSettings = new System.Text.Json.JsonSerializerOptions
{
    PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
    WriteIndented = true,
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers = 
        {
            Modifiers.OnlyBaseExceptionProperties,
            static typeInfo =>
            {
                if (typeInfo.Type == typeof(Exception))
                {
                    // 添加需要处理的Exception子类
                    typeInfo.PolymorphismOptions = new JsonPolymorphismOptions
                    {
                        IgnoreUnrecognizedTypeDiscriminators = true,
                        DerivedTypes =
                        {
                            new JsonDerivedType(typeof(ValidationException)),
                            new JsonDerivedType(typeof(BaseException))
                        }
                    };
                }
            }
        }
    }
};

// 修改Modifier的类型判断逻辑
public static class Modifiers
{
    public static void OnlyBaseExceptionProperties(System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo)
    {
        Debug.WriteLine($"Type in System.Text.Json: {typeInfo.Type}");

        // 判断改为:只要类型是BaseException或其子类就应用规则
        if (!typeof(BaseException).IsAssignableFrom(typeInfo.Type))
        {
            return;
        }

        var allowedProperties = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
        {
            nameof(BaseException.Type),
            nameof(BaseException.Details),
            nameof(BaseException.Description)
        };

        foreach (var property in typeInfo.Properties)
        {
            property.ShouldSerialize = allowedProperties.Contains(property.Name) 
                ? static (_, _) => true 
                : static (_, _) => false;
        }
    }
}

方案3:自定义JsonTypeInfoResolver(进阶)

继承DefaultJsonTypeInfoResolver,重写GetTypeInfo方法,强制优先使用运行时类型:

public class RuntimeTypeJsonTypeInfoResolver : DefaultJsonTypeInfoResolver
{
    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
    {
        // 针对非密封引用类型,优先获取实际子类的TypeInfo
        if (!type.IsSealed && type.IsClass)
        {
            // 这里可以根据业务需求扩展,比如仅处理Exception相关类型
            return base.GetTypeInfo(type, options);
        }
        return base.GetTypeInfo(type, options);
    }
}

// 使用自定义Resolver
var textJsonSettings = new System.Text.Json.JsonSerializerOptions
{
    // ...其他配置
    TypeInfoResolver = new RuntimeTypeJsonTypeInfoResolver
    {
        Modifiers = { Modifiers.OnlyBaseExceptionProperties }
    }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 18:55:26