Search code examples
c#asp.net-coreasp.net-web-apiswaggerswashbuckle

Swagger unexpected API PATCH action documentation of JsonPatchDocument in example request body


I'm making a Core 3.1 web API and using JsonPatch to create a PATCH action. I have an action named Patch which has a JsonPatchDocument parameter. Here is the action's signature:

[HttpPatch("{id}")]
public ActionResult<FileRecordDto> Patch(int id, [FromBody] JsonPatchDocument<FileRecordQueryParams> patchDoc)

As I understand, the parameter needs to receive JSON data in the following structure, which I've successfully tested with the action:

[
  {
    "op": "operationName",
    "path": "/propertyName",
    "value": "newPropertyValue"
  }
]

However, the action's documentation generated by Swagger has a different structure: enter image description here

I'm not familiar with this structure and even "value" property is missing from it, which a JsonPatchDocument object has. Every example of patching with the replace operation I've seen has had the first structure.

Why is Swagger generating an alternate structure for a JsonPatchDocument object in the request body for the PATCH endpoint? How do I fix this?

The NuGet package installed for Swagger: Swashbuckle.AspNetCore v5.6.3


Solution

  • Swashbuckle.AspNetCore doesn't work propertly with this type JsonPatchDocument<UpdateModel>, which doesn’t represent the expected patch request doument.

    You need to custome a document filter to modify the generated specification.

    public class JsonPatchDocumentFilter : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            var schemas = swaggerDoc.Components.Schemas.ToList();
            foreach (var item in schemas)
            {
                if (item.Key.StartsWith("Operation") || item.Key.StartsWith("JsonPatchDocument"))
                    swaggerDoc.Components.Schemas.Remove(item.Key);
            }
    
            swaggerDoc.Components.Schemas.Add("Operation", new OpenApiSchema
            {
                Type = "object",
                Properties = new Dictionary<string, OpenApiSchema>
                {
                    {"op", new OpenApiSchema{ Type = "string" } },
                    {"value", new OpenApiSchema{ Type = "string"} },
                    {"path", new OpenApiSchema{ Type = "string" } }
                }
            });
    
            swaggerDoc.Components.Schemas.Add("JsonPatchDocument", new OpenApiSchema
            {
                Type = "array",
                Items = new OpenApiSchema
                {
                    Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Operation" }
                },
                Description = "Array of operations to perform"
            });
    
            foreach (var path in swaggerDoc.Paths.SelectMany(p => p.Value.Operations)
            .Where(p => p.Key == Microsoft.OpenApi.Models.OperationType.Patch))
            {
                foreach (var item in path.Value.RequestBody.Content.Where(c => c.Key != "application/json-patch+json"))
                    path.Value.RequestBody.Content.Remove(item.Key);
                var response = path.Value.RequestBody.Content.Single(c => c.Key == "application/json-patch+json");
                response.Value.Schema = new OpenApiSchema
                {
                    Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "JsonPatchDocument" }
                };
            }
        }
    }
    

    Register the filter:

    services.AddSwaggerGen(c => c.DocumentFilter<JsonPatchDocumentFilter>());
    

    Result:

    enter image description here