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

从Newtonsoft迁移到System.Text.Json时保留DataAnnotations行为的方法

.NET 8 WebAPI迁移System.Text.Json时required关键字验证提示差异问题

问题场景

我正在将一个使用Newtonsoft.Json的.NET 8 WebAPI迁移到System.Text.Json,遇到了required关键字相关的验证提示差异,以下用默认的WeatherForecast示例说明:

Program.cs

// 省略部分代码
builder.Services.AddControllers();
// 省略部分代码

WeatherForecast.cs

public class WeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    [Required]
    public required string Summary { get; set; }
}

WeatherForecastController.cs

// 省略部分代码
[HttpPost(Name = "WeatherForecast")]
public IActionResult Post(WeatherForecast request)
{
    return Ok(request);
}

差异表现

当通过Swagger调用Post接口,请求体未传入Summary属性时:

{
  "date": "2024-01-01",
  "temperatureC": 1
}

System.Text.Json的错误返回

{
  "errors": {
    "$": [
      "JSON deserialization for type 'NewtonsoftVsSystemTextLab.WeatherForecast' was missing required properties, including the following: summary"
    ],
    "request": [
      "The request field is required."
    ]
  }
}

Newtonsoft.Json的错误返回(仅需添加.AddNewtonsoftJson()扩展方法)

{
  "errors": {
    "Summary": [
      "The Summary field is required."
    ]
  }
}

提问

有没有无需修改源代码的非侵入式方案,让System.Text.Json输出和Newtonsoft.Json一致的友好验证提示?


非侵入式解决方案

可以通过配置JsonOptions并添加自定义模型验证过滤器实现,全程不需要改动实体类代码:

1. 关闭System.Text.Json的内置required属性验证

在Program.cs中修改控制器配置,让System.Text.Json不再自动校验required属性,转而交给DataAnnotations的[Required]特性处理:

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        // 关闭required属性的反序列化强制校验
        options.JsonSerializerOptions.RequiredPropertyHandling = JsonRequiredPropertyHandling.Ignore;
        // 保留原有Json配置(比如驼峰命名、大小写不敏感)
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    });

2. 编写自定义验证过滤器

创建一个过滤器,捕获System.Text.Json的反序列化错误,将其转换为DataAnnotations风格的字段级错误提示:

using Microsoft.AspNetCore.Mvc.Filters;
using System.Text.RegularExpressions;

public class RequiredPropertyValidationFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            var errorsToRemove = new List<string>();
            var correctedErrors = new Dictionary<string, List<string>>();

            foreach (var modelError in context.ModelState)
            {
                // 处理根节点的反序列化错误(键为"$"的条目)
                if (modelError.Key == "$")
                {
                    foreach (var error in modelError.Value.Errors)
                    {
                        // 从错误信息中提取缺失的属性名
                        var match = Regex.Match(error.ErrorMessage, @"including the following: (\w+)");
                        if (match.Success)
                        {
                            var camelCaseProp = match.Groups[1].Value;
                            // 转换为实体类的PascalCase属性名
                            var pascalCaseProp = char.ToUpper(camelCaseProp[0]) + camelCaseProp.Substring(1);
                            if (!correctedErrors.ContainsKey(pascalCaseProp))
                            {
                                correctedErrors[pascalCaseProp] = new List<string>();
                            }
                            correctedErrors[pascalCaseProp].Add($"The {pascalCaseProp} field is required.");
                        }
                    }
                    errorsToRemove.Add(modelError.Key);
                }
                // 移除无意义的"request"字段错误
                else if (modelError.Key == "request")
                {
                    errorsToRemove.Add(modelError.Key);
                }
            }

            // 清理旧的错误条目
            foreach (var key in errorsToRemove)
            {
                context.ModelState.Remove(key);
            }

            // 添加转换后的字段级错误
            foreach (var entry in correctedErrors)
            {
                context.ModelState.AddModelError(entry.Key, entry.Value.First());
            }
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // 无需处理后续逻辑
    }
}

3. 注册自定义过滤器

在Program.cs的控制器配置中添加这个过滤器:

builder.Services.AddControllers(options =>
{
    options.Filters.Add<RequiredPropertyValidationFilter>();
})
.AddJsonOptions(options =>
{
    options.JsonSerializerOptions.RequiredPropertyHandling = JsonRequiredPropertyHandling.Ignore;
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});

配置完成后,再发送缺失Summary的请求,System.Text.Json就会返回和Newtonsoft.Json完全一致的错误提示:

{
  "errors": {
    "Summary": [
      "The Summary field is required."
    ]
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:33:18