ASP.NET Core 6使用Newtonsoft.Json如何容忍单个属性反序列化错误
问题根因
你配置的args.ErrorContext.Handled = true不生效,和你提到的Newtonsoft.Json已知问题无关,核心原因有两点:
- ASP.NET Core默认提供的
NewtonsoftJsonInputFormatter逻辑是:反序列化过程中只要捕获到向上冒泡的异常,就直接终止整个流程,把绑定的模型置为null,哪怕部分属性已经反序列化成功也会直接丢弃 - 你现在写的全局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
相关产品推荐
相关产品推荐

