describe a bool or DateTime map key as the string serde writes, where the key's own type is no property key - #236
Merged
Conversation
… the key's own type is no property key
A map keyed by `bool` or `DateTime<Tz>` was described at the key's own type on
both nominal surfaces, and neither survives contact with a compiler or a
payload. `Partial<Record<boolean, V>>` and `Partial<Record<Date, V>>` are hard
TS2344 — neither type is a property key — and the emitted Zod fails the same
way at its own construction. At runtime the bool key was rejected outright
(`z.boolean()` handed the string `"true"`), and the DateTime key was worse:
`z.coerce.date()` accepted it and rewrote every key to a locale-dependent
`Date.toString()`, corrupting the map silently with no round-trip.
serde writes both keys as strings — `true` as `"true"`, a `DateTime` as its
RFC 3339 text — and the fix is to describe that wire instead of the type:
bool Partial<Record<"true" | "false", V>>
z.partialRecord(z.enum(["true", "false"]), V)
DateTime Partial<Record<string, V>>
z.record(z.iso.datetime({ offset: true }), V)
The answer comes from one seam both surfaces read, and a name forwards its
target's answer, so a brand over `bool`, an alias over `DateTime`, and an alias
of a brand all render the same wire form their inner does. The registry entry
that had collapsed brand-over-bool with brand-over-number now carries the wire
form beside the kind it already recorded. The Zod side yields the whole record
call rather than the key alone, since the constructor moves with the key. JSON
Schema is untouched — its open object never claimed the key's type. The
self-referencing spellings compose: a bool key takes the mapped-type branch,
a DateTime key the index signature.
Verified on the verbatim emitted text: tsc strict exits 0 where it produced
eleven errors, and zod 4.4.3 parses the real serde payload with every key
preserved byte for byte. Both constructors exist at zod 4.0.0, the documented
floor. String-, number- and enum-keyed maps are pinned byte-identical.
just fmt, just check, just quick, just lint-all across all 68 toggles, and the
test powerset across all 68 in four partitions — all green.
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.
A map keyed by
boolorDateTime<Tz>was described at the key's own type onboth nominal surfaces, and neither survives contact with a compiler or a
payload.
Partial<Record<boolean, V>>andPartial<Record<Date, V>>are hardTS2344 — neither type is a property key — and the emitted Zod fails the same
way at its own construction. At runtime the bool key was rejected outright
(
z.boolean()handed the string"true"), and the DateTime key was worse:z.coerce.date()accepted it and rewrote every key to a locale-dependentDate.toString(), corrupting the map silently with no round-trip.serde writes both keys as strings —
trueas"true", aDateTimeas itsRFC 3339 text — and the fix is to describe that wire instead of the type:
The answer comes from one seam both surfaces read, and a name forwards its
target's answer, so a brand over
bool, an alias overDateTime, and an aliasof a brand all render the same wire form their inner does. The registry entry
that had collapsed brand-over-bool with brand-over-number now carries the wire
form beside the kind it already recorded. The Zod side yields the whole record
call rather than the key alone, since the constructor moves with the key. JSON
Schema is untouched — its open object never claimed the key's type. The
self-referencing spellings compose: a bool key takes the mapped-type branch,
a DateTime key the index signature.
Verified on the verbatim emitted text: tsc strict exits 0 where it produced
eleven errors, and zod 4.4.3 parses the real serde payload with every key
preserved byte for byte. Both constructors exist at zod 4.0.0, the documented
floor. String-, number- and enum-keyed maps are pinned byte-identical.
just fmt, just check, just quick, just lint-all across all 68 toggles, and the
test powerset across all 68 in four partitions — all green.