Mastering Polymorphic OpenAPI Responses with oneOf & Discriminator
When an API returns different event shapes, documenting them clearly is hard. This post shows how OpenAPI 3.x’s oneOf and discriminator can model such polymorphic responses, with a live example, trade‑offs, and best‑practice checklist.
13 May 2026, 05:13 UTC

Concrete Problem: Heterogeneous Event Payloads
Imagine a payment platform that exposes a single /events endpoint. Each event can be a CardCharged, RefundIssued, or DisputeOpened object. The JSON payload shape varies per type, but the consumer must know which fields belong to which event. If the spec only lists a generic object schema or relies on documentation snippets, developers end up guessing or writing ad‑hoc parsers.
Documenting this correctly is essential for type‑safe client generation, clear API docs, and automated validation. The OpenAPI 3.x spec offers two tools for this: oneOf composition and the discriminator keyword.
Thesis: Use oneOf + Discriminator to Model Tagged Unions
In OpenAPI 3.x, oneOf asserts that a value must validate against exactly one of the provided subschemas. When paired with a discriminator, the spec tells tooling which property selects the correct subschema. This matches the common “tagged union” pattern in many APIs.
Section 1 – Modeling Heterogeneous Responses
The discriminator keyword takes two parts:
propertyName– the JSON property that holds the tag value (e.g.,eventType).mapping– (optional) a map of tag values to schema references when the tag value does not match the subschema name.
Below is a minimal OpenAPI 3.0.x snippet that demonstrates this for a GET /events endpoint.
openapi: 3.0.3
info:
title: Payment Events API
version: 1.0.0
paths:
/events:
get:
summary: Retrieve the latest event
responses:
'200':
description: A single event
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/CardChargedEvent'
- $ref: '#/components/schemas/RefundIssuedEvent'
- $ref: '#/components/schemas/DisputeOpenedEvent'
discriminator:
propertyName: eventType
mapping:
CARD_CHARGED: '#/components/schemas/CardChargedEvent'
REFUND_ISSUED: '#/components/schemas/RefundIssuedEvent'
DISPUTE_OPENED: '#/components/schemas/DisputeOpenedEvent'
components:
schemas:
CardChargedEvent:
type: object
required: [eventType, amount, currency]
properties:
eventType:
type: string
enum: [CARD_CHARGED]
amount:
type: number
currency:
type: string
RefundIssuedEvent:
type: object
required: [eventType, refundId, originalAmount]
properties:
eventType:
type: string
enum: [REFUND_ISSUED]
refundId:
type: string
originalAmount:
type: number
DisputeOpenedEvent:
type: object
required: [eventType, disputeId, reason]
properties:
eventType:
type: string
enum: [DISPUTE_OPENED]
disputeId:
type: string
reason:
type: string
Key points:
- Each subschema declares the
eventTypeenum to enforce the tag value. - The
discriminator.mappingis optional if the tag value matches the subschema name; it’s useful when the tag string differs. - The
oneOfensures the payload matches exactly one subschema, preventing accidental overlap.
Section 2 – Tooling Impact
When you run a validator or generate a client, the discriminator guides the process:
- Validation –
swagger-cli validate spec.yaml(oropenapi-cli validate spec.yaml) will check thateventTypematches one of the mapped values and that the rest of the object conforms to the corresponding subschema. If a payload contains a field from two subschemas, validation fails. - Client Generation – Tools like
openapi-generator(Java, TypeScript, Go) can emit sealed types or union interfaces. For example, runningopenapi-generator generate -i spec.yaml -g typescript-fetch -o ./clienttypically produces aEventunion type that discriminates oneventType. - Documentation Renderers – Swagger‑UI or Redoc will show the
oneOfschema as a dropdown of possible event shapes, with the discriminator highlighted.
However, support is uneven. Some generators ignore the discriminator.mapping and generate a generic any type, while others produce nested interfaces that are hard to consume. Always inspect the generated code or run a small unit test to confirm the discriminated type behaves as expected.
Section 3 – Trade‑offs & Limitations
- Version Sensitivity – OpenAPI 3.1 aligns more closely with JSON Schema 2020‑12, which changes some composition semantics. If you target 3.1, double‑check that your tooling still respects
oneOfanddiscriminatorthe same way. - Complexity vs. Simplicity – Overusing
oneOfcan make the spec harder to read and the generated code more verbose. In some cases, a flat schema with atypefield and optional properties may be clearer for both humans and tools. - Validation Performance – Runtime validators that check every subschema can be slower, especially with many variants. If performance is critical, consider a two‑step approach: first verify the discriminator value, then validate the chosen subschema.
- Tool Coverage – Not all validators (e.g.,
swagger-cli) supportdiscriminator.mapping, and some client generators produce code that requires manual type guards. Test early in your CI pipeline.
Actionable Checklist
- Define a clear
discriminator.propertyNamethat all variants share. - Use
oneOfto list all possible subschemas. - Optional but recommended: provide
discriminator.mappingfor explicit tag‑to‑schema links. - Validate the spec with
swagger-cli validateand run a quick client generation test. - Review generated code to ensure the discriminated type is usable.
- Document the expected
eventTypevalues in the README or API guide.
By structuring your OpenAPI spec this way, you give consumers a clear contract, enable type‑safe clients, and reduce the risk of misinterpreting heterogeneous payloads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.