OpenAPI in Dotnet 10: Number quirk
Did you already upgrade your API to Dotnet 10, and are you using OpenAPI specs? Then you might want to read on the next information. We use the Microsoft.AspNetCore.OpenApi package to generate openapi specifications for our api, which is in turn used to generate client code for various platforms.
I’m not sure when this started, but it seems numbers are now specified to be either a number or a string with a regex pattern. This causes issues in the generated code and we need it gone.
Number or string
The issue we found in the specs (this is not a complete specification, just a small snippet):
{
"components": {
"schemas": {
"SomeObject": {
"type": "object",
"properties": {
"order": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
},
....
}
}
}
}
}
This snippet shows the schema of SomeObject with a property order, in our code this is an int and should thus be represented as a integer. This will never be represented as a string, because dotnet does not (yet) allow union types. Having the specs tell that this might be represented as a string is faulty and I want it gone.
IntIsNoStringSchemaTransformer
Since we are talking schema’s here, we need some entry point to modify the schemas in this app. This is where the IOpenApiSchemaTransformer comes in handy.
Create a new IntIsNoStringSchemaTransformer.cs file in your project and paste in the following code.
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
namespace MyApi.Transformers;
/// <summary>
/// Provides an OpenAPI schema transformer that ensures schemas with the format "int32" and "double" are represented as number types
/// rather than strings.
/// </summary>
/// <remarks>This transformer is typically used to correct schemas where integer/double values may have been incorrectly
/// modeled as strings with an "int32" / "double" format. It modifies the schema in place to use the appropriate integer type,
/// which can help ensure accurate code generation and validation in OpenAPI-based tools.</remarks>
public class IntIsNoStringSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (//schema.Type == JsonSchemaType.Integer | JsonSchemaType.String &&
schema.Format == "int32")
{
schema.Type = JsonSchemaType.Integer;
schema.Format = "int32";
schema.Pattern = null;
}
if (schema.Format == "double")
{
schema.Type = JsonSchemaType.Number;
schema.Pattern = null;
}
return Task.CompletedTask;
}
}
The if statement might be improved, but I’m not sure how to check for schema.Type == ["integer","string"], any suggestions send me a message on LinkedIn.
Register transformer
Just creating the transformer does not help, you’ll need to register it. Open your Program.cs and change the .AddOpenApi(..) call.
builder.Services.AddOpenApi(config =>
{
config.AddSchemaTransformer(new MyApi.Transformers.IntIsNoStringSchemaTransformer());
//config.AddOperationTransformer(new MyApi.Transformers.SomeOperationTransformer());
//config.AddDocumentTransformer(new MyApi.Transformers.SomeDocumentTransformer());
});
After registering, your transformer will be called for each schema in the openapi specs. And this is where you can modify everything your want about it.
Is this a bug?
I’m not sure if this is a bug or just the result of an enthusiastic programmer that expressed the way dotnet can parse the value anyway even if numbers are represented as strings.
First order of business was fixing the issue with the client not being generated. Second order was sharing this knowledge with the rest of you. Maybe I’m going to investigate where this string representation comes from and create an issue about it. That is however not the priority right now.