Swashbuckle.AspNetCore
Swashbuckle.AspNetCore copied to clipboard
How to map Optional<T>
Please excuse, if this is not the right spot to post. It is unclear if I just don't know how to use the api correctly, if swashbuckle is missing a feature, or if it is a bug.
I'm using Optional<T>
in my DTOs to implement Json Patch (details). The type T is stored in the 'Value' field of the Optional<T>
class. The Optional class allows to differentiate between null and not provided.
public readonly struct Optional<T>
{
public Optional(T? value)
{
this.HasValue = true;
this.Value = value;
}
public bool HasValue { get; }
public T? Value { get; }
public static implicit operator Optional<T>(T value) => new Optional<T>(value);
public override string ToString() => this.HasValue ? (this.Value?.ToString() ?? "null") : "unspecified";
}
The first DTOs I worked with held only primitive types, e.g.
public class PatchGroupDTO
{
public Optional<Guid?> SalesGroupId { get; init; }
public Optional<string?> Name { get; init; }
}
The example that Swagger UI displays is wrong, because it displays the property "value":
{
"salesGroupId": {
"value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"name": {
"value": "string"
}
}
Our solution was to map the types in Startup.cs.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "xxx.API", Version = "v1" });
c.MapType<Optional<Guid?>>(() => new OpenApiSchema { Type = "string", Format = "uuid" });
c.MapType<Optional<string?>>(() => new OpenApiSchema { Type = "string" });
c.MapType<Optional<bool?>>(() => new OpenApiSchema { Type = "boolean" });
c.MapType<Optional<int?>>(() => new OpenApiSchema { Type = "integer" });
})
The example is then correctly displayed as:
{
"salesGroupId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name":"string"
}
In my latest DTO I needed nested objects. The simplified DTO looks like this:
public record UpdateCartDTO
{
public Optional<AddressDTO?> InvoicingAddress { get; init; }
public Optional<List<CommentDTO>?> Comments { get; init; }
public Optional<List<string>?> ReservationCodes { get; init; }
};
The example will wrongly show the nested value property again:
{
"invoicingAddress": {
"value": {
"type": "string",
"receipient": "string",
"deliveryInstructio": "string",
"street": "string",
"streetNumber": "string",
"streetAffix": "string",
"zip": "string",
"city": "string",
"state": "string",
"isO3": "string"
}
},
"comments": {
"value": [
{
"language": "string",
"comment": "string"
}
]
},
"reservationCodes": {
"value": [
"string"
]
}
}
List<string>
can be mapped with:
c.MapType<Optional<List<string>?>>(
() => new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Type="string" } });
I have problems to map the nested objects. I tried:
c.MapType<Optional<AddressDTO?>>(() => new OpenApiSchema { Type = "object" });
But this only shows empty braces {}
. The way I understand the api I would need to manually define all the types of the AddressDTO with Item = new OpenApiSchema{...}
. Is there any solution that can just refer to the AddressDTO?
It is in general cumbersome to define a MapType for every T that is used inside Optional<T>. Is there any way to have a generic mapping from Optional<T> to T? Something like:
c.MapType<Optional<T?>>(() => new OpenApiSchema { Object= "T"});