-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Incorrect OpenAPI Schema Generation for Recursive Types #61139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
We have this same issue, and it is making it difficult to use the generated spec file with automated deployment tools that actually expect... a valid OpenAPI spec file. I see that there have been multiple related issues on this subject but nothing has been done about it. |
Btw, @sorcerb, I don't know if you're interested in hearing this, but you can temporarily fix this by using SchemaTransformers. You can add a schema transformer to your OpenApiOptions instance which finds schemas containing "properties" in its reference ID, and then choose how to fix it (in our case using the supplied OpenApiSchemaTransformerContext to find the type which the property refers to, and then just set the reference ID to that type. It's not necessarily super pretty, and there might be other ways of doing it better. But this works for our use case so far. |
I had to create a middleware for the
public class FixProjectsOpenApiMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Path == "/openapi/v1.json")
{
var originalBody = context.Response.Body;
using var memory = new MemoryStream();
context.Response.Body = memory;
await next(context);
if (
context.Response is { StatusCode: 200 }
&& MediaTypeHeaderValue.TryParse(
context.Response.Headers.ContentType.ToString(),
out var contentType
)
&& contentType.MediaType == "application/json"
)
{
memory.Position = 0;
UpdateOpenApiV1Schema(memory);
memory.Position = 0;
context.Response.ContentLength = memory.Length;
}
await originalBody.WriteAsync(memory.GetBuffer().AsMemory(0, (int)memory.Length));
context.Response.Body = originalBody;
}
else
{
await next(context);
}
}
private static void UpdateOpenApiV1Schema(MemoryStream stream)
{
var jsonNode = JsonNode.Parse(stream);
if (jsonNode is not null)
{
var resourcesSchema = jsonNode["components"]
?["schemas"]
?["Nav"]
?["properties"]
?["children"];
if (resourcesSchema is not null)
{
resourcesSchema["items"] = new JsonObject
{
["$ref"] = "#/components/schemas/Nav",
};
stream.Position = 0;
stream.SetLength(0);
JsonSerializer.Serialize(
stream,
jsonNode,
new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // Use with caution
}
);
}
}
}
} It could be parameterized if more than one case is found in your API. |
Found this bug today with 9.04 version too and as suggested by @CodingBeagle I used a SchemaTransformer as workaraound public class NavMenuFixSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (context.JsonTypeInfo.Type == typeof(NavMenuItem))
{
if (schema.Properties.TryGetValue("children", out OpenApiSchema? value))
{
value.Nullable = false;
value.Items = new OpenApiSchema
{
Reference = new OpenApiReference
{
Id = nameof(NavMenuItem),
Type = ReferenceType.Schema
}
};
}
}
return Task.CompletedTask;
}
} and in the service initialization builder.Services.AddOpenApi(options =>
{
options.AddSchemaTransformer<NavMenuFixSchemaTransformer>();
}); |
@damianog thankyou, it's work for me. So is it a bug, or is it the expected behavior for OpenAPI? Should someone add a bug label to this issue? |
Hi @sorcerb here a generic version of my schema transformer to fix recursive types public class RecursiveTypesSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
var type = context.JsonTypeInfo.Type;
var recursiveTypes = context.JsonTypeInfo.Properties
.Where(p =>
p.PropertyType.IsGenericType
&& typeof(IEnumerable).IsAssignableFrom(p.PropertyType)
&& p.PropertyType.GetGenericArguments().Contains(type)
);
foreach (var recursiveType in recursiveTypes)
{
if (schema.Properties.TryGetValue(recursiveType.Name, out OpenApiSchema? value))
{
value.Nullable = recursiveType.IsGetNullable;
value.Items = new OpenApiSchema
{
Reference = new OpenApiReference
{
Id = recursiveType.DeclaringType.Name,
Type = ReferenceType.Schema
}
};
}
}
return Task.CompletedTask;
}
} |
Is there an existing issue for this?
Describe the bug
We encountered an issue where ASP.NET Core incorrectly generates OpenAPI json schema for recursive types. Specifically, the generated schema for a recursive model (Nav) is valid, but when attempting to define a separate object (Nav2) that references an array of items, it incorrectly resolves the $ref path.
Expected Behavior
The generated OpenAPI schema should correctly resolve $ref references for recursive types, ensuring Nav2.children.items properly refers to the Nav schema.
Steps To Reproduce
Exceptions (if any)
No response
.NET Version
9.0.3
Related issues
Recursive data models inside collections yield invalid schema references #59879
.NET 9 OpenAPI produces lots of duplicate schemas for the same object #58968
The text was updated successfully, but these errors were encountered: