Skip to content

fix(typescript-zod): preserve null for nullable date-time fields#2966

Merged
schani merged 1 commit into
masterfrom
agent/fix-issue-2880
Jul 20, 2026
Merged

fix(typescript-zod): preserve null for nullable date-time fields#2966
schani merged 1 commit into
masterfrom
agent/fix-issue-2880

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

The TypeScript-Zod renderer emitted nullable date-time fields as z.union([z.coerce.date(), z.null()]). Zod tries union members in order, and z.coerce.date() happily coerces null into new Date(null) (the Unix epoch, 1970-01-01T00:00:00.000Z) instead of failing, so the z.null() branch was never reached. A JSON field with createdAt: null round-tripped as an epoch timestamp instead of staying null.

Root cause

In packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts, the union-type case built the z.union([...]) member list in whatever order unionType.getChildren() returned them, with no regard for the fact that Zod's coercing schemas (like z.coerce.date()) will happily accept null and coerce it, silently shadowing a z.null() member placed after it.

Fix

Sort the union's children so z.null() is emitted first in the generated z.union([...]) call. Verified directly against the installed zod package:

z.union([z.coerce.date(), z.null()]).parse(null) // => 1970-01-01T00:00:00.000Z (WRONG, pre-fix)
z.union([z.null(), z.coerce.date()]).parse(null) // => null (correct, post-fix)
z.union([z.null(), z.coerce.date()]).parse("2023-01-15T10:30:00Z") // => Date object (still works)

Test coverage

test/inputs/json/priority/bug2590.json (added in #2879 for an unrelated Dart bug) already contains an object with a nullable date-time field, including an array element where it's null. It was previously skipped for typescript-zod in test/languages.ts with a comment pointing at this exact issue (// z.coerce.date() coerces null to the Unix epoch: #2880). This PR removes that skip entry so the fixture now runs for typescript-zod, exercising this regression end-to-end (generate → compile → round-trip via zod's parse/serialize).

Verification

  • Reproduced the bug before the fix: node dist/index.js -l typescript-zod sample.json on bug2590.json emitted z.union([z.coerce.date(), z.null()]); confirmed at the zod runtime level that .parse(null) returned an epoch Date.
  • Implemented the fix, rebuilt (npm run build passes), and confirmed the generated code now emits z.union([z.null(), z.coerce.date()]), with .parse(null) correctly returning null and normal date-time strings still parsing to Date objects.
  • Ran the full typescript-zod fixture suite locally: QUICKTEST=true FIXTURE=typescript-zod script/test — all 45/45 tests passed, including bug2590.json.

Fixes #2880

🤖 Generated with Claude Code

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
@schani
schani merged commit 47f5a83 into master Jul 20, 2026
28 checks passed
@schani
schani deleted the agent/fix-issue-2880 branch July 20, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeScript-Zod coerces nullable date-time null to epoch

1 participant