OpenAPI errors

How to fix discriminator and mapping errors in OpenAPI oneOf

A discriminator mapping error usually isn't a validator error at all — that's what makes it worth writing down. discriminator.mapping is a free-form string-to-string map that most validators don't cross-check against the oneOf list it's supposed to describe. Two real mistakes fall out of that: a mapping key pointing at a schema that doesn't exist, and a mapping key pointing at a schema that exists but was never added to oneOf. Only the first one reliably produces a validator error. The second one — an "orphan" mapping entry — passed every validator we tested and then broke a real code generator's compiled output. Reproduced with real tools on 2026-09-21.

What does the spec say discriminator and mapping do?

Per the OpenAPI specification, a discriminator object is only meaningful alongside oneOf, anyOf, or allOf, and it names two fields: propertyName (the payload field that identifies which subschema applies) and an optional mapping (a table from the property's possible values to specific schemas, for when the value doesn't match the schema's own name). Without mapping, tooling falls back to matching the discriminator value against schema names directly. With mapping, the table is authoritative — and nothing in the spec text requires every value in mapping to also appear in the surrounding oneOf.

That gap is the whole problem: mapping and oneOf are two independent lists describing the same set of subtypes, and nothing enforces that they agree.

How do you reproduce the "unresolved mapping" error?

The straightforward mistake — a mapping value that's a broken $ref — behaves exactly like an unresolved reference anywhere else in a schema (see the unresolved $ref post for the general case):

PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CreditCard'
    - $ref: '#/components/schemas/BankTransfer'
  discriminator:
    propertyName: type
    mapping:
      credit_card: '#/components/schemas/CreditCard'
      bank_transfer: '#/components/schemas/BankTransfer'
      paypal: '#/components/schemas/PayPalAccount'   # PayPalAccount doesn't exist

swagger-cli validate v4.0.4: reports the file as valid — a bare JSON-Schema check has no concept of discriminator at all, so a broken pointer inside mapping is invisible to it.

redocly lint v2.53.3 (default ruleset): catches it —

[1] api.yaml:41:19 at #/components/schemas/PaymentMethod/discriminator/mapping/paypal

Can't resolve $ref

Error was generated by the no-unresolved-refs rule.

Same rule, same error shape as any other unresolved $refno-unresolved-refs checks mapping values along with every other $ref-shaped field in the document.

How do you reproduce the silent "orphan mapping" bug?

This is the more dangerous version. Add PayPalAccount as a real, fully-defined component schema — but leave it out of the oneOf list, only referencing it from mapping:

PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CreditCard'
    - $ref: '#/components/schemas/BankTransfer'
    # PayPalAccount is missing here
  discriminator:
    propertyName: type
    mapping:
      credit_card: '#/components/schemas/CreditCard'
      bank_transfer: '#/components/schemas/BankTransfer'
      paypal: '#/components/schemas/PayPalAccount'   # exists, but not in oneOf above

We ran this exact spec through both validators. swagger-cli validate reports it valid. redocly lint, with its full default ruleset, also reports it valid — zero errors, zero warnings related to the mismatch. The $ref resolves fine (the schema exists), so no-unresolved-refs has nothing to flag, and neither tool has a rule that cross-checks mapping keys against the oneOf array's members.

We then generated a TypeScript client from that exact spec with OpenAPI Generator CLI 7.25.0 (typescript-fetch). The generator picked up PayPalAccount from the mapping entry — correctly including it in the generated union type and the discriminated switch statements — but never added the corresponding import statement, because its import list is built from the (incomplete) oneOf array. The generated PaymentMethod.ts compiles into a file referencing a symbol it never imports:

export type PaymentMethod = { type: 'bank_transfer' } & BankTransfer
  | { type: 'credit_card' } & CreditCard
  | { type: 'paypal' } & PayPalAccount;   // PayPalAccount is never imported

Running that file through the TypeScript compiler (tsc --noEmit) produces real, reproduced compiler errors:

models/PaymentMethod.ts(36,132): error TS2304: Cannot find name 'PayPalAccount'.
models/PaymentMethod.ts(52,38): error TS2304: Cannot find name 'PayPalAccountFromJSONTyped'.
models/PaymentMethod.ts(72,38): error TS2304: Cannot find name 'PayPalAccountToJSON'.

The generated package doesn't build. Nothing in the spec-validation step warned this was coming.

How do you fix it?

Keep mapping and oneOf in sync — every schema named in mapping must also appear in oneOf:

# Fixed — PayPalAccount is in both places
PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CreditCard'
    - $ref: '#/components/schemas/BankTransfer'
    - $ref: '#/components/schemas/PayPalAccount'
  discriminator:
    propertyName: type
    mapping:
      credit_card: '#/components/schemas/CreditCard'
      bank_transfer: '#/components/schemas/BankTransfer'
      paypal: '#/components/schemas/PayPalAccount'

With PayPalAccount back in oneOf, the same OpenAPI Generator run adds the missing import, and the generated file compiles cleanly.

How different tools react

Check Broken $ref in mapping Schema exists but missing from oneOf
swagger-cli validate (schema-only) Passes (blind to discriminator) Passes
redocly lint (default ruleset) Fails — no-unresolved-refs Passes — no cross-check rule
OpenAPI Generator CLI (typescript-fetch) N/A — would fail earlier at resolution Compiles broken output, no build-time warning
tsc --noEmit on the generated package N/A Fails — TS2304: Cannot find name

The orphan-mapping case only surfaces three steps downstream of the spec, in a language server or compiler most spec authors never run against generated output before shipping.

How to catch this before it ships

  1. Treat mapping and oneOf as one list you maintain together, not two — when you add a new polymorphic subtype, add it to both in the same commit.
  2. Don't rely on a default linter ruleset to catch an orphan mapping entry — as reproduced above, redocly lint's default rules don't cross-check mapping against oneOf. If your team hits this often, write it up as a custom Spectral rule.
  3. Actually compile the generated SDK in CI, not just generate it. openapi-generator generate exiting 0 means template rendering succeeded, not that the output type-checks — the real signal here was tsc, not the generator.
  4. Run the free in-browser OpenAPI validator as a first pass for the cases it does catch (broken $refs inside mapping), then generate and compile before calling the spec done.

If you're shipping SDKs with oneOf polymorphism, Sourced generates and compiles the package as part of every preview, which is what actually catches an orphan mapping entry — a green spec validation with a broken generated client is exactly the gap Sourced's report is built to close. Create hosted docs from your repo or start a free report.

FAQ

Does OpenAPI require every oneOf schema to appear in mapping?

No. If mapping is omitted entirely, tools fall back to matching the discriminator's value against schema names directly. mapping is only needed when the payload's discriminator value doesn't match a schema name, or when you want an explicit table instead of implicit name-matching.

Does OpenAPI require every mapping entry to appear in oneOf?

Not per the specification text, but in practice it should — a mapping entry that isn't backed by a member of the composite keyword (oneOf/anyOf/allOf) it's attached to describes a subtype the schema doesn't actually declare as valid, which is exactly the orphan-mapping bug reproduced above.

Why didn't redocly lint catch the orphan mapping case?

Its default ruleset checks that every $ref — including ones inside mapping — resolves to something real. It doesn't include a rule that cross-references mapping keys against the oneOf array's members, because that's a semantic consistency check specific to discriminator usage, not a general reference-resolution check.

Is this specific to TypeScript generators?

The exact failure (a missing import) is TypeScript-shaped, but the root cause — a generator trusting an inconsistent mapping/oneOf pair — can misfire in any generator. A statically typed generator (Java, Go, Rust) is more likely to fail loudly at compile time, the way tsc did here; a dynamically typed one may fail silently at runtime instead, only when it actually receives a paypal-typed payload.

Can discriminator be used without oneOf, anyOf, or allOf?

No — per the specification, the discriminator object is only meaningful alongside one of those three composite keywords. A discriminator field on a plain schema with none of them has no defined effect.

What's the safest way to add a new polymorphic subtype to an existing discriminator?

Add the new schema to components/schemas, add it to the oneOf array, and add its mapping entry, all in the same change — then regenerate and actually compile the SDK before merging, since that's the step that would have caught the orphan-mapping bug reproduced in this post.