Polymorphism: oneOf, anyOf, allOf, discriminator

OpenAPI · Advanced · 15 min

Real payloads have variants, and how you model them decides whether SDKs get usable types.

JSON Schema composition keywords:

  • allOf: must match all subschemas. Used to extend a base schema (allOf: [$ref Pet, {properties: {...}}]).
  • oneOf: must match exactly one subschema. Used for alternatives like payment methods.
  • anyOf: must match at least one. Rarely what you want for typed SDKs.
  • not: must not match.

oneOf is strict: if two alternatives both accept an object (e.g. neither forbids extra properties), the payload matches both and validation fails. Make variants mutually exclusive with required fields, const values or additionalProperties: false.

A discriminator names the property that tells variants apart, e.g. type: card. It is an OpenAPI addition (not JSON Schema) that helps code generators and validators choose the right class quickly. With mapping you connect values to schemas explicitly; otherwise the schema name is used. In 3.2, defaultMapping adds a fallback.

Give each variant a const for its discriminator value, so the document validates correctly even for tools that ignore discriminator.

Generated code quality varies a lot across generators with deep allOf chains, so keep composition shallow and test what your SDK generator produces.

Example

components:
  schemas:
    Payment:
      oneOf:
        - $ref: '#/components/schemas/CardPayment'
        - $ref: '#/components/schemas/BankPayment'
      discriminator:
        propertyName: method
        mapping:
          card: '#/components/schemas/CardPayment'
          bank: '#/components/schemas/BankPayment'
    PaymentBase:
      type: object
      required: [method, amountCents]
      properties:
        amountCents: { type: integer, minimum: 1 }
    CardPayment:
      allOf:
        - $ref: '#/components/schemas/PaymentBase'
        - { type: object, required: [cardToken], properties: { method: { const: card }, cardToken: { type: string } } }
    BankPayment:
      allOf:
        - $ref: '#/components/schemas/PaymentBase'
        - { type: object, required: [iban], properties: { method: { const: bank }, iban: { type: string } } }

Remember

  • allOf = all, oneOf = exactly one, anyOf = at least one
  • Make oneOf variants mutually exclusive or validation fails
  • A discriminator names the property that selects the variant
  • Use const on the discriminator property in each variant

Watch out for

  • oneOf variants that overlap, so valid payloads fail with 'matches more than one schema'.

Check yourself

  1. A payload satisfies two schemas listed under `oneOf`. What is the result?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: spec.openapis.org/oas/v3.1.1.html#discriminator-object