OpenAPI errors

How to fix mixed-type enum errors in OpenAPI

An enum with mixed value types — some numbers, some strings, no explicit type on the schema — is valid JSON Schema and valid OpenAPI. But when a code generator writes those raw values into a source file without checking whether each one still needs quotes, you can get generated TypeScript that doesn't compile at all. Reproduced end-to-end, spec through a real compiler error, on 2026-09-21; the fix is to make every value in the enum the same JSON type.

What does the actual compiler error look like?

Starting from this schema — an enum with three numbers and one bare string, no type keyword:

openapi: 3.0.3
info:
  title: Demo API
  version: 1.0.0
paths:
  /widgets/{id}:
    get:
      operationId: getWidget
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  priority:
                    enum: [1, 2, 3, "urgent"]

Both swagger-cli validate v4.0.4 and redocly lint v2.53.3 accept this file with no complaint about the enum itself (Redocly's default ruleset flags unrelated warnings like a missing summary, not the mixed types). Running it through OpenAPI Generator v7.25.0 (-g typescript-fetch) generates this real, unaltered TypeScript:

export const GetWidget200ResponsePriorityEnum = {
    NUMBER_1: 1,
    NUMBER_2: 2,
    NUMBER_3: 3,
    NUMBER_urgent: urgent,
} as const;

Compiling that file with tsc produces a real compiler error:

error TS2304: Cannot find name 'urgent'.

The generator's member-naming logic prefixed every value with NUMBER_ — reasonable when every value actually is a number — but wrote the string value urgent on the right-hand side without quotes, because its naming branch assumed a numeric enum. The result is a bare identifier reference instead of a string literal, and TypeScript looks for a variable named urgent that doesn't exist anywhere in scope.

Why does mixing types in one enum cause this?

Because enum in JSON Schema — which OpenAPI's Schema Object inherits — never required every value to share a type. The JSON Schema validation spec defines enum simply as "an array... Elements in the array might be of any type, including null" with no constraint tying the values to a single declared type. That's intentional flexibility for JSON Schema in general, but code generators targeting a statically-typed enum construct (a TypeScript const object, a Java enum, a Python Enum class) generally assume — implicitly, in their naming and serialization logic — that every member is the same primitive type. When that assumption breaks, whatever the generator's type-specific code path does (numeric-prefix naming, unquoted literals, wrong member type) breaks along with it.

This is a real gap between what the spec permits and what a single-language enum construct can represent cleanly. It's the same category of gap as OpenAPI 3.1's type: [string, "null"] nullable pattern — the spec allows more shapes than any one generated language construct can express without special-casing — except a mixed-type enum is easier to introduce by accident, usually by appending one more allowed value (a new status string, a sentinel number) without checking whether it matches the type of everything already in the list.

How do you fix it?

Make every value in the enum the same JSON type — here, all strings, since "urgent" can't become a number without losing meaning:

# Before — mixed numbers and a string; generates code that fails to compile
properties:
  priority:
    enum: [1, 2, 3, "urgent"]

# After — all strings, with an explicit type
properties:
  priority:
    type: string
    enum: ["1", "2", "3", "urgent"]

Regenerating from the fixed spec with the same generator and version produces:

export const GetWidget200ResponsePriorityEnum = {
    _1: '1',
    _2: '2',
    _3: '3',
    Urgent: 'urgent',
} as const;

Every value is a properly quoted string literal, member names are derived consistently, and tsc --noEmit against the generated file exits clean with no errors.

If the field genuinely needs to represent two different kinds of values — say, a small set of named states plus an arbitrary numeric code — model it as oneOf with two typed branches instead of one mixed enum, and give each branch a type. That's a more honest representation of "this can be one of two different shapes" than forcing both into a single enum list.

How to catch this before it ships

  1. Grep your spec for enum: blocks and check every listed value shares a type — a mix of quoted and unquoted YAML scalars in one enum array is the visual tell ([1, 2, 3, "urgent"] mixes bare numbers with a quoted string).
  2. Always pair enum with an explicit type on the schema — most linters, including Redocly's default ruleset, won't flag a type-less enum on their own, so this is a self-imposed convention, not something you can rely on a linter to force.
  3. Actually compile a generated TypeScript client at least once when a spec's enums change, not just build it — a schema-valid spec can still generate code that fails tsc, as reproduced above, and that only shows up at the compile step, not the validation step.
  4. Run the spec through the free in-browser OpenAPI validator before generation — it surfaces enum values inline so a type mismatch is visible while you're still editing the spec.

If you're generating a TypeScript SDK from a spec where enum values have drifted over time — a common source of exactly this kind of mixed-type list — Sourced's hosted docs and SDK pipeline previews the actual generated TypeScript before you publish, catching a compile failure before a customer does. Start a free report.

FAQ

Is a mixed-type enum invalid OpenAPI?

No. JSON Schema's enum keyword explicitly allows values of any type in the same array, and OpenAPI inherits that. Both swagger-cli validate and redocly lint's default ruleset accept a mixed-type enum with no error, as reproduced above — this is a code-generation problem, not a spec-validity problem.

Why did the generated code compile-fail instead of just producing a wrong value?

Because the generator's member-naming logic assumed every enum value was numeric (it prefixed all four members with NUMBER_) and wrote the one string value without quotes on that assumption. The result — urgent instead of 'urgent' — is a bare JavaScript identifier reference, which tsc correctly rejects since no variable named urgent exists.

Does adding type: string to the schema fix a mixed-type enum on its own?

Not by itself — type: string alongside enum: [1, 2, 3, "urgent"] would make the numeric values invalid against their own declared type, which a schema validator would then flag. You need to make the enum values themselves consistent (quote the numbers as strings, as shown in the fix) to match whichever type you declare.

What about a nullable enum — does mixing null with other types cause the same problem?

It's related but distinct — see our OpenAPI nullable vs type: null post for that specific case. In testing for this post, a nullable: true enum with a literal null entry generated correctly (TypeScript's | null union), because that's a pattern generators explicitly special-case; an enum mixing arbitrary non-null types like numbers and strings is the less commonly handled case.

Should I ever intentionally mix types in one enum?

Rarely. If a field can hold two genuinely different kinds of values — for example, a known set of string states plus an arbitrary numeric error code — oneOf with two typed branches documents that intent explicitly and generates cleanly in every mainstream generator, instead of relying on one enum list to carry two different meanings.

Will every generator fail the same way on a mixed-type enum?

The specific failure (an unquoted string literal) is this generator's implementation detail, but the underlying risk is general: any generator whose enum-handling logic branches on an assumed single type is exposed to some failure mode — a compile error, a silently wrong serialized value, or a runtime type mismatch — when that assumption doesn't hold.