ASP.NET Core 3.1 API中JsonPatch无法正常工作的技术求助
你遇到的序列化错误,核心原因是默认的JsonPatchDocument<T>期望直接接收操作数组作为请求体(比如[{"op":"replace",...},...]),但你的契约把操作数组包裹在了items字段下,再加上System.Text.Json和Newtonsoft.Json的配置冲突,导致自动绑定逻辑无法正确识别嵌套的数组结构。下面是具体的解决步骤:
第一步:确保Newtonsoft.Json的Patch支持正确配置
首先确认你已经安装了Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet包(ASP.NET Core 3.1默认不包含这个,需要手动安装)。然后在Startup.cs中,不仅要定义格式化器方法,还要把它添加到Mvc的输入格式化器集合中,确保优先使用Newtonsoft的处理逻辑:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => { // 将自定义的Newtonsoft格式化器插入到最前面,确保优先处理Patch请求 options.InputFormatters.Insert(0, GetJsonPatchInputFormatter()); }) .AddNewtonsoftJson(options => { options.SerializerSettings.DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset; }); } // 你已有的格式化器方法保持不变 private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter() { var builder = new ServiceCollection() .AddLogging() .AddMvc() .AddNewtonsoftJson(_ => _.SerializerSettings.DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset) .Services.BuildServiceProvider(); return builder .GetRequiredService<IOptions<MvcOptions>>() .Value .InputFormatters .OfType<NewtonsoftJsonPatchInputFormatter>() .First(); }
第二步:调整请求类与绑定逻辑
因为你的请求结构是外层对象包含items数组,而JsonPatchDocument<T>本质是操作的集合,这里提供两种可行的适配方式:
方式一:强类型请求类+手动转换(推荐)
先定义匹配请求结构的强类型类,再手动将操作列表转换为JsonPatchDocument<T>,保证类型安全:
// 匹配请求中单个Patch操作的类 public class PatchOperation { public string Op { get; set; } public string Path { get; set; } public object Value { get; set; } } // 你的请求契约类 public class UpdateCustomerRequest { public List<PatchOperation> Items { get; set; } }
然后在控制器方法中,将操作转换为JsonPatchDocument并应用到目标对象:
[HttpPatch("{customerId}")] [SwaggerOperation("UpdateCustomer")] [SwaggerResponse((int)HttpStatusCode.MultiStatus)] public async Task<IActionResult> UpdateCustomer(Guid customerId, [FromBody] UpdateCustomerRequest request) { var patchDoc = new JsonPatchDocument<CustomerPatchDocument>(); foreach (var op in request.Items) { switch (op.Op.ToLowerInvariant()) { case "replace": // 根据Path匹配对应的属性,这里以Name为例,可扩展其他属性 if (op.Path.Equals("/name", StringComparison.OrdinalIgnoreCase)) { patchDoc.Replace(p => p.Name, op.Value.ToString()); } break; case "add": // 处理add操作逻辑 break; case "remove": // 处理remove操作逻辑 break; // 其他操作类型按需添加 } } // 示例:将Patch应用到目标对象 var targetCustomer = new CustomerPatchDocument(); patchDoc.ApplyTo(targetCustomer); // 后续的保存、返回逻辑 return Ok(targetCustomer); }
方式二:手动反序列化(快速解决)
如果不想定义额外的操作类,可以直接用JObject接收请求,手动提取items数组并反序列化为JsonPatchDocument<T>:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; [HttpPatch("{customerId}")] [SwaggerOperation("UpdateCustomer")] [SwaggerResponse((int)HttpStatusCode.MultiStatus)] public async Task<IActionResult> UpdateCustomer(Guid customerId, [FromBody] JObject request) { // 提取items数组并反序列化为JsonPatchDocument var itemsJson = request["items"].ToString(); var patchDoc = JsonConvert.DeserializeObject<JsonPatchDocument<CustomerPatchDocument>>(itemsJson); // 应用Patch到目标对象 var targetCustomer = new CustomerPatchDocument(); patchDoc.ApplyTo(targetCustomer); // 后续逻辑 return Ok(targetCustomer); }
为什么之前的配置失败?
你之前直接用JsonPatchDocument<CustomerPatchDocument>作为Items属性类型,但默认序列化器会把JsonPatchDocument<T>识别为数组类型,而你的请求中items是对象的一个数组属性,这种嵌套结构无法被自动映射到JsonPatchDocument<T>,因此触发了序列化错误。
内容的提问来源于stack exchange,提问作者user729400

