Skip to content

OpenAPI: Generate anyOf schemas for C# union types #66544

@DeagleGross

Description

@DeagleGross

Update OpenAPI document generation to correctly represent C# union types in the generated specification. Union types should produce anyOf schemas composed from their case types.

Related: #55412, #56177

Expected behavior

Given an endpoint:

union ApiResult(Product, ProblemDetails);

app.MapGet("/products/{id}", (int id) => { ... })
    .Produces<ApiResult>(200);

The OpenAPI spec should produce:

paths:
  /products/{id}:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/Product'
                  - $ref: '#/components/schemas/ProblemDetails'

For a simple value union:

union Result(int, string);
schema:
  anyOf:
    - type: integer
    - type: string

Areas to investigate

  1. Schema generation path:
    STJ's JsonSchemaExporter already emits anyOf for union types (driven by JsonTypeInfo.UnionCases). The OpenAPI layer uses JsonSchemaExporter under the hood (OpenApiSchemaService), so the raw schema should come out correctly. The work is ensuring the OpenAPI layer correctly maps the STJ schema output to OpenAPI constructs.

  2. No discriminator:
    Unlike [JsonPolymorphic] types which generate oneOf + discriminator in OpenAPI, unions should produce anyOf with no discriminator object. This matches STJ's transparent serialization (no $type on the wire).

  3. Response metadata expansion:
    When an endpoint returns a union type, the response metadata currently shows the union type itself. Two approaches:

    • Option A: Keep union as a single response type, let the schema layer expand to anyOf via JsonSchemaExporter — simpler, schema is auto-generated
    • Option B: Expand union cases into individual ProducesResponseTypeMetadata entries at the metadata layer (leveraging MVC+OpenAPI: Support multiple Produces for same status code / content-type #65650 infrastructure) — more explicit, each case type appears separately in the response
  4. Request body schemas:
    When a union is a request body parameter, the schema should similarly show anyOf for the parameter type.

Metadata

Metadata

Assignees

Labels

area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-openapifeature-request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions