Reputation: 2213
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:
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:
Upvotes: 9
Views: 6234
Reputation: 575
Here's the updated code for OpenAPI3
(OAS3
- new SwaggerUI
version) that handles schemas and paths.
public class JsonPatchDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Handle schemas
var keysToRemove = swaggerDoc.Components.Schemas
.Where(s => s.Key.StartsWith("SystemTextJsonPatch", StringComparison.OrdinalIgnoreCase))
.Select(s => s.Key)
.ToList();
foreach (var key in keysToRemove)
{
swaggerDoc.Components.Schemas.Remove(key);
}
swaggerDoc.Components.Schemas.Add("JsonPatchDocument", new OpenApiSchema
{
Type = "object",
Description = "Describes a single operation in a JSON Patch document. Includes the operation type, the target property path, and the value to be used.",
Properties = new Dictionary<string, OpenApiSchema>
{
{
"op", new OpenApiSchema
{
Type = "string",
Description = "The operation type. Allowed values: 'add', 'remove', 'replace', 'move', 'copy', 'test'.",
}
},
{
"path", new OpenApiSchema
{
Type = "string",
Description = "The JSON Pointer path to the property in the target document where the operation is to be applied.",
}
},
{
"value", new OpenApiSchema
{
Type = "string",
Description = "The value to apply for 'add', 'replace', or 'test' operations. Not required for 'remove', 'move', or 'copy'.",
}
},
},
});
// Handle paths
foreach (var path in swaggerDoc.Paths)
{
if (path.Value.Operations.TryGetValue(OperationType.Patch, out var patchOperation) && patchOperation.RequestBody != null)
{
foreach (var key in patchOperation.RequestBody.Content.Keys)
{
patchOperation.RequestBody.Content.Remove(key);
}
if (patchOperation.OperationId.StartsWith("odata", StringComparison.OrdinalIgnoreCase))
{
path.Value.Operations.Remove(OperationType.Patch);
}
patchOperation.RequestBody.Content.Add("application/json-patch+json", new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "JsonPatchDocument" },
},
});
}
}
}
}
Previous UI:
And schemas
looked like:
Current version is:
Remember to change the Name
of the HttpPatch
attribute in your Controller and leave JsonPatchDocument
schema name unchanged.
Upvotes: 0
Reputation: 8459
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:
Upvotes: 14