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

ASP.NET Core 6使用Newtonsoft.Json如何容忍单个属性反序列化错误

问题根因

你配置的args.ErrorContext.Handled = true不生效,和你提到的Newtonsoft.Json已知问题无关,核心原因有两点:

  1. ASP.NET Core默认提供的NewtonsoftJsonInputFormatter逻辑是:反序列化过程中只要捕获到向上冒泡的异常,就直接终止整个流程,把绑定的模型置为null,哪怕部分属性已经反序列化成功也会直接丢弃
  2. 你现在写的全局Error事件没有做错误层级判断,属性级的类型转换错误(比如字符串转int失败)会一路冒泡到对象、集合层级,最终触发格式化器的整体失败判定。

解决步骤

1. 调整JsonSerializerSettings配置,拦截属性级错误

修改SerializationOptionConfigurator里的配置,补全兼容旧ASP.NET MVC行为的设置,同时在Error委托里只处理属性/字段级别的错误,阻止错误向上冒泡:

public virtual void Configure(MvcNewtonsoftJsonOptions options)
{
    options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    // 兼容旧MVC行为:忽略多余字段、支持基础类型的自动转换
    options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
    options.SerializerSettings.Converters.Add(new StringEnumConverter());
    options.UseMemberCasing();
    
    options.SerializerSettings.Error += (sender, args) =>
    {
        // 仅处理单个属性/字段的反序列化错误,不处理对象、集合层级的错误
        if (args.ErrorContext.Member != null 
            && args.CurrentObject == args.ErrorContext.OriginalObject)
        {
            Program.Logger.Error(args.ErrorContext.Error, 
                "字段{0}反序列化失败,已填充默认值", 
                args.ErrorContext.Member.Name);
            args.ErrorContext.Handled = true;
        }
    };

    options.SerializerSettings.Converters.Add(new DateTimeJsonConverter(_accessor));
}

2. 替换默认的JSON输入格式化器,避免半成功的反序列化结果被丢弃

默认的格式化器只要检测到反序列化过程中出现过错误,就会直接丢弃整个模型,必须自定义格式化器重写这段逻辑:

public class FaultTolerantNewtonsoftJsonFormatter : NewtonsoftJsonInputFormatter
{
    public FaultTolerantNewtonsoftJsonFormatter(
        ILogger logger,
        JsonSerializerSettings serializerSettings,
        ArrayPool<char> charPool,
        ObjectPoolProvider objectPoolProvider,
        MvcOptions mvcOptions,
        MvcNewtonsoftJsonOptions jsonOptions)
        : base(logger, serializerSettings, charPool, objectPoolProvider, mvcOptions, jsonOptions)
    {
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context, 
        Encoding encoding)
    {
        var result = await base.ReadRequestBodyAsync(context, encoding);
        // 只要模型对象不为空,就算有属性级错误也正常返回,不丢弃已成功绑定的内容
        if (result.Model != null)
        {
            return InputFormatterResult.Success(result.Model);
        }
        return result;
    }
}

在Startup的Mvc配置里把默认的格式化器替换成自定义的容错版本:

services.AddMvc(options =>
{
    // 移除默认的NewtonsoftJson格式化器
    var defaultFormatter = options.InputFormatters
        .OfType<NewtonsoftJsonInputFormatter>()
        .First();
    options.InputFormatters.Remove(defaultFormatter);
    
    // 注册自定义容错格式化器
    var sp = services.BuildServiceProvider();
    options.InputFormatters.Add(new FaultTolerantNewtonsoftJsonFormatter(
        logger: sp.GetRequiredService<ILogger<FaultTolerantNewtonsoftJsonFormatter>>(),
        serializerSettings: GetYourPreConfiguredSerializerSettings(), // 复用你之前配置的序列化设置
        charPool: ArrayPool<char>.Shared,
        objectPoolProvider: new DefaultObjectPoolProvider(),
        mvcOptions: options,
        jsonOptions: new MvcNewtonsoftJsonOptions()
    ));
}).AddNewtonsoftJson();

补充说明

配置完成后行为会和旧ASP.NET MVC保持一致:

  • 单个属性反序列化失败时,该字段会被赋值为对应类型的默认值,错误信息会写入ModelState
  • 其余反序列化成功的属性会正常保留,不会出现整个入参为null的情况
  • 如果是顶层集合类型入参(比如你示例中的IEnumerable<Appointment>),单个元素的属性错误不会导致整个集合绑定失败。

如果不想自定义格式化器,也可以通过实现自定义IModelBinder的方式实现同样效果,但自定义输入格式化器的改造成本最低,也最贴近原有Newtonsoft.Json的配置逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 12:18:23