配置JsonSerializerOptions后模型绑定未按预期工作的问题咨询
嘿,我来帮你梳理清楚这个问题的来龙去脉~
你在DI容器里配置了JsonOptions来设置JSON序列化规则,代码是这样的:
builder.Services.Configure<JsonOptions>(options => { options.SerializerOptions.AllowTrailingCommas = false; options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.SerializerOptions.PropertyNameCaseInsensitive = false; });
然后写了个最小API的Post端点,用Product record作为参数:
app.MapPost("/product", (Product product) => $"Received {product}"); record Product(int Id, string Name, int Stock);
测试时发现返回的是Received Product { Id = 0, Name = , Stock = 0 },框架没抛异常,但属性值全是默认值,你疑惑为什么——尤其是你以为发送和record属性名完全一致的JSON(比如{"Id": 1, "Name": "Shoes", "Stock": 12})应该能正常绑定,但实际却没生效。
问题的核心原因
这里有几个关键要点需要理清:
PropertyNamingPolicy的双向影响
你设置的PropertyNamingPolicy = JsonNamingPolicy.CamelCase不仅会影响输出序列化(把对象的PascalCase属性转成JSON的驼峰键),还会影响输入反序列化:框架会先把JSON里的键名转换成驼峰格式,再和你的Product属性名(PascalCase)做匹配。
比如你发送{"Id": 1},框架会先把Id转换成驼峰的id,再去匹配Product的Id属性。大小写严格匹配的限制
你同时设置了PropertyNameCaseInsensitive = false,这意味着属性名的大小写必须完全一致才能匹配。上面的场景中,转换后的id和Product的Id大小写不匹配,自然绑定失败,所有属性就会使用各自类型的默认值(int默认0,string默认空)。最小API的默认行为
最小API默认不会因为模型绑定失败抛出异常,而是会创建一个对象的默认实例返回,这就是为什么你没看到报错,但得到了空值的Product。
另外还要确认:你在Postman发送请求时,有没有把Content-Type头设置为application/json?如果没设置这个头,框架会无法识别请求体是JSON格式,同样会返回默认的Product实例。
解决办法
根据你的需求,有几种调整方案:
方案1:希望JSON使用PascalCase键绑定
移除PropertyNamingPolicy = JsonNamingPolicy.CamelCase的配置,这样反序列化时不会转换键名,配合PropertyNameCaseInsensitive = false,就只会严格匹配完全一致的键名:
builder.Services.Configure<JsonOptions>(options => { options.SerializerOptions.AllowTrailingCommas = false; // 移除驼峰命名策略 // options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.SerializerOptions.PropertyNameCaseInsensitive = false; });
这时发送{"Id": 1, "Name": "Shoes", "Stock": 12}就能正常绑定了。
方案2:保留驼峰命名策略(比如输出用驼峰)
如果你想继续用驼峰策略输出JSON,有两种选择:
- 发送驼峰格式的JSON请求体,比如
{"id": 1, "name": "Shoes", "stock": 12},此时框架会把id正确映射到Id属性; - 把
PropertyNameCaseInsensitive设为true,这样大小写不敏感,即使JSON键是Id,转换后也能匹配Id属性。
方案3:添加模型绑定失败的校验
如果你想在绑定失败时明确返回错误,可以给Product添加验证特性,并在端点里检查模型状态:
// 给record添加必填验证 record Product([Required] int Id, [Required] string Name, [Required] int Stock); // 修改端点检查模型状态 app.MapPost("/product", ([FromBody] Product product, IModelStateDictionary modelState) => { if (!modelState.IsValid) { return Results.ValidationProblem(modelState); } return Results.Ok($"Received {product}"); });
这样绑定失败时就会返回详细的验证错误信息,而不是默认实例。
备注:内容来源于stack exchange,提问作者yinivem462

