UnitsNet
UnitsNet copied to clipboard
Swagger schema reports all unit properties and not UnitValue ones
Describe the bug When using UnitsNetIQuantityJsonConverter (and hence Newtonsoft.JSon serializer), Swagger generates the schema of all units (e.g. Length) as they are as C# classes, reporting all their properties (value, unit, feetInches, quantityInfo, dimensions and all related subobjects). Bug UnitsNetIQuantityJsonConverter convert values such that they are (de)serialized as ValueUnit, i.e. having value and unit properties only.
To Reproduce Steps to reproduce the behavior: Use this solution: WebTest.zip
It exposes a single method as a Rest API (/Api), returning a MyDto object, having Location = 12.345 Km. Try calling /Api. The result will correctly be:
{
"location": {
"unit": "LengthUnit.Kilometer",
"value": 12.345
}
}
But MyDto will have Example Values:
{
"location": {
"feetInches": {
"feet": 0,
"inches": 0
},
"value": 0,
"unit": 1,
"quantityInfo": {
"name": "string",
"unitInfos": [
{
"value": 1,
"pluralName": "string",
"baseUnits": {
"length": 1,
"mass": 1,
"time": 1,
"current": 1,
"temperature": 1,
...
}
I truncated it a lot, since it is veeeeeery long (more than 360 lines). Please note that this is not coherent with the JSON actually returned by the API. This is the bug!
And MyDto will have schema:
MyDto{
location
Length{
feetInches FeetInches{...}
value [...]
unit LengthUnit[...]
quantityInfo LengthUnitQuantityInfo{...}
dimensions BaseDimensions{...}
}
}
Expected behavior Example Values should be:
{
"location": {
"value": 0,
"unit": "string"
}
}
Schema should be:
MyDto{
location
Length
{
value number($double)
unit string
}
}
Additional context The problem is very easy to spot and seems easy to fix: we are using a converter, but Swagger has no idea of it. We just need to inform Swagger about the substitution of all IQuantityValue with ValueUnit (ExtendedValueUnit, in some cases).
I fixed it (to some extents) using an ISchemaFilter:
In Program.cs:
builder.Services.AddSwaggerGen(c =>
{
c.SchemaFilter<QuantitySerializationFilter>();
});
The ISchemaFilter is defined in this way:
public class QuantitySerializationFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (typeof(IQuantity).IsAssignableFrom(context.Type))
{
schema.Properties = schema.Properties.Where(p => p.Key == "value" || p.Key == "unit").ToDictionary(k => k.Key, k => k.Value);
schema.Properties["unit"].Type = "string";
schema.Properties["unit"].Reference = null;
}
}
}
It works: now I have the right Example Values and Schema. But I don't think this is a complete fix, since:
- Swagger keeps producing the schemas of AmountOfSubstanceUnit, Assembly, BaseDimensions, etc.
- The above code has the properties value and unit hardwired, while they should be taken dynamically from the UnitValue type.
Thanks for sharing. I don't have any knowledge on this specific problem with Swagger, but I would recommend you to consider creating your own data transfer DTO types for representing quantities. Then you are in full control of its serialization, and you can remove all the cruft that exists in these types, since you typically just need Value and Unit properties. Just create a simple mapping function to/from QuantityDto
and IQuantity
.
The DTO could be as simple as:
public record QuantityDto(double Value, string QuantityType, string Unit);
// Example:
QuantityDto dto = new(Value: 5.0, QuantityType: "Length", Unit: "Centimeter");
Then you don't need any special serializers injected here and there to make things work.
Also, if we incur a breaking change in our JSON serializer, which we do really try to avoid, then your API gets a breaking change.
I realize it was not obvious how to accomplish this, so I created some helper methods Quantity.From()
and .TryFrom()
that can construct quantities from strings, and updated the wiki with an example.
New methods added: https://github.com/angularsen/UnitsNet/pull/1258
Wiki: https://github.com/angularsen/UnitsNet/wiki/Serializing-to-JSON,-XML-and-more#-recommended-map-to-your-own-custom-dto-types
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.
This issue was automatically closed due to inactivity.