You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add properties checks for instanceof schemas (#5912)
* Add properties checks for instanceof schemas
* Revert "Add properties checks for instanceof schemas"
Replaced by a plain z.properties(shape) helper that returns an array of
ordinary $ZodCheckProperty checks. The dedicated $ZodCheckProperties check
type had no case in core/compile.ts, so any schema using it dropped off the
AOT fast path, and SupportedCheck there is a hand-listed union so nothing
caught the omission.
* Add z.properties() for checking several properties at once
Returns an array of ordinary $ZodCheckProperty checks to spread into the
existing variadic .check(), so there is no new check type, no new constructor,
and no per-schema-type method. Each check carries `when`, which opts it out of
the run loop's aborted-issue gate, so a parse reports every property that failed
rather than only the first.
* fix(v4): drop the always-run gate from z.properties
`when` opts a check past the *implicit* abort gate, not just the sibling-property
one. `runChecks` skips a `when`-carrying check only on `util.explicitlyAborted`, but
`z.object` / `z.record` / `z.array` push their type-mismatch issues with no `continue`
field, so the property checks ran against a value the base schema had already
rejected — and `$ZodCheckProperty` indexes `payload.value` unguarded.
const obj = z.object({ a: z.string() }).check(...z.properties({ a: z.literal("x") }));
obj.safeParse(null); // TypeError: Cannot read properties of null (reading 'a')
obj.safeParse(5); // invalid_type [] plus a spurious invalid_value ["a"]
A throwing `safeParse` is not a trade worth making for aggregated issue paths, so the
checks are now plain: `z.properties(shape)` is exactly the array of `z.property()`
calls it looks like, and behaves identically to writing them out. That also puts the
schema back on the compiled fast path, since `compile.ts` rejects any check carrying a
non-defaulted `when`.
The z.instanceof tests missed this because `_instanceof` aborts explicitly. The object
cases now cover null, undefined and a primitive.
* test(v4): pin where z.properties is looser than the longhand
Every element is typed over the whole shape, and `$ZodCheckInternals.check()` is a
method, so TypeScript compares it bivariantly and accepts a check type that is a
subtype of the target. Naming a key the target lacks compiles as a result, and fails
at parse time instead — where the equivalent chain of `z.property()` calls rejects it
outright.
Typing each element over only its own key restores the diagnostic and breaks every
valid call, because a union argument has to satisfy the target on its own. Pinning the
gap so it reads as a known trade rather than an accident.
// Not specific to z.instanceof(). A base whose type mismatch aborts implicitly rather than explicitly must still not run the property checks against the rejected value.
// Known looseness versus the longhand, pinned so it stays deliberate: every element is typed over the whole shape, and `$ZodCheckInternals.check()` is a method, so TypeScript compares it bivariantly and accepts a check type that is a subtype of the target. Naming a key the target lacks therefore compiles here and fails at parse time, where the equivalent chain of `z.property()` calls rejects it outright. Typing each element over only its own key fixes that and breaks every valid call, since a union argument must satisfy the target on its own.
0 commit comments