Repository navigation
fix: keep every value of a multi-value literal in the generated enum - #391
Open
Motoki-sai wants to merge 1 commit into
Open
Motoki-sai wants to merge 1 commit into
Motoki-sai wants to merge 1 commit into
Conversation
Since Zod 4 a ZodLiteral can hold multiple values (z.literal([0, 1])), but the transformer only emitted def.values[0], so every value but the first was silently dropped from the generated document. The type is now derived from all values: it is omitted when they do not share one, and `null` is left to the nullable mapping, which also makes z.literal(null) consistent with z.null().
joyeshonubi-star
approved these changes
Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Since Zod 4 a
ZodLiteralcan hold multiple values (z.literal([0, 1])), butLiteralTransformerreads onlydef.values[0]:Every value but the first is silently dropped from the generated document, so a schema that accepts
0or1is documented as accepting only0:isCrossingschema{ "type": "number", "enum": [0] }{ "type": "number", "enum": [0, 1] }z.toJSONSchema(for reference){ "type": "number", "enum": [0, 1] }It reproduces on
master(9.1.0) for both string and number literals, and in 3.0.0, 3.1.0 and 3.2.0 alike. Nothing throws — the document is just wrong, which makes it easy to miss.I hit this through
@hono/zod-openapi, where a query parameter documented asenum: [0]still validated1at runtime.Fix
enumnow contains all values, and the type is derived from all of them:z.literal([0, 'john'])) get no type, which is whatz.toJSONSchemadoes as wellnullis excluded from the type computation and left to the nullable mapping, soz.literal([0, null])stays{ type: 'number', nullable: true, enum: [0, null] }BigIntTransformer(they cannot be represented asenumvalues)One incidental behaviour change:
z.literal(null)used to produce{ type: 'object', nullable: true, enum: [null] }becausetypeof null === 'object'. It now produces{ nullable: true, enum: [null] }in 3.0.0 and{ enum: [null] }in 3.1.0, which matches whatz.null()generates. It had no test, andtype: 'object'for anullvalue looked unintended, but happy to restore it if you consider it part of the contract.Tests
Added
spec/types/literal.spec.ts(there was no dedicated spec for literals): single string/number/boolean/bigint values, multiple values per type, nullable, mixed types, and the null literal in 3.0.0 and 3.1.0.npx jestpasses in full (52 suites) andnpm run test:typesis clean.