Modeling Polymorphic Payloads in OpenAPI 3.x with oneOf and a Discriminator
How to model polymorphic request and response bodies in OpenAPI 3.x using oneOf with a discriminator: a worked Pet/Cat/Dog schema, why the mapping table and required property matter, and the pitfalls that break validators and generators.
07 May 2026, 04:38 UTC

If your API accepts or returns more than one shape for the same field — say a Pet that is either a Cat or a Dog — the supported way to describe that in OpenAPI 3.x is oneOf combined with a discriminator object. The discriminator names a property in the payload (for example petType) whose value tells validators and code generators which concrete schema applies. Done correctly, this gives you deterministic routing of payloads; done carelessly, it produces schemas that pass validation in one tool and fail in another.
The mechanism: oneOf plus a named selector property
oneOf means "the payload must match exactly one of these subschemas." On its own, a validator has to try each subschema until one matches, which is slow and can be ambiguous if two subschemas both accept the payload. The discriminator solves this by declaring up front: "look at this property, and its value tells you which schema to use." It is an optimization and a disambiguation hint, not a validation rule by itself — the payload still has to actually validate against the selected subschema.
The subschemas typically share a common base using allOf, so shared fields are defined once. Here is a complete, conventional OpenAPI 3.0 example:
components:
schemas:
Pet:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: petType
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'
Cat:
allOf:
- $ref: '#/components/schemas/PetBase'
- type: object
properties:
huntingSkill:
type: string
Dog:
allOf:
- $ref: '#/components/schemas/PetBase'
- type: object
properties:
packSize:
type: integer
PetBase:
type: object
required:
- petType
- name
properties:
petType:
type: string
name:
type: stringA valid payload then looks like {"petType": "cat", "name": "Mog", "huntingSkill": "lazy"}. A validator reads petType, consults the mapping, and validates against Cat only.
Why the explicit mapping table matters
The mapping key is optional. Without it, tooling assumes the discriminator value is identical to the schema name — so the value cat would be expected to resolve to a schema literally named cat, not Cat. If your wire values differ from your schema names in case or convention (very common: lowercase enum values, PascalCase schemas), omitting the mapping fails silently in some tools and loudly in others. Always write the mapping explicitly; it costs three lines and removes an entire class of cross-tool inconsistency.
Make the discriminator property required
The discriminator property must be present in the payload for selection to work, but OpenAPI does not force you to declare it required. Many validators skip discriminator enforcement when the property is optional, which means a payload with no petType can be accepted as matching one of the subschemas by accident — or rejected with a confusing "matched more than one schema" error. Declaring it required in the base schema (as in the example above) makes the failure mode explicit and early: "missing required property petType" is a far better error than an ambiguity report.
How tooling interprets this
Code generators such as openapi-generator map the hierarchy to inheritance in typed languages: Cat and Dog become subclasses of Pet, and deserialization logic switches on petType. Two practical caveats follow. First, generated clients often do not populate the discriminator field automatically on serialization unless petType is a real, settable property of the model — so a client can emit a Cat with no petType and get rejected by a strict server. Check the generated code and, if needed, set the field explicitly or configure the generator's discriminator options. Second, runtime validators differ on oneOf exclusivity: a payload that structurally matches both Cat and Dog (possible if the subschemas are loosely defined) may pass in one gateway and fail in another. Keep subschemas mutually exclusive — distinct required fields or a strict discriminator enum help.
Common mistakes
- Using anyOf with a discriminator.
anyOfmeans "match at least one," which has no well-defined selection semantics. The discriminator is only meaningful alongsideoneOf(orallOfcompositions). Mixing them produces undefined behavior across tools. - Discriminator property not defined as a real property. The property must exist in the schema's
properties, not just be named in the discriminator object. - Conflicting properties across allOf layers. Deep compositions can redefine the same property with different types; different tools resolve the conflict differently. Define each property once.
- Assuming the mapping is enforced. Some generators ignore the mapping table entirely and fall back to schema-name matching. Test with your actual toolchain.
Version note and how to verify
The example above targets OpenAPI 3.0. OpenAPI 3.1 aligns with JSON Schema 2020-12 and adjusts some discriminator nuances, and generator behavior changes between releases — verify against the versions you actually run. A practical verification loop: paste the schema into the Swagger Editor (editor.swagger.io) or editor.openapis.org and confirm it renders without structural errors; run your request-validation middleware with a payload missing petType and confirm it is rejected; send a payload whose petType matches no mapping entry and confirm it is flagged; and generate a client, then serialize a Cat and inspect whether petType appears in the JSON. Those four checks cover the failure modes that actually bite in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.