Summary
makeJsonSchema() in packages/zod/src/factory.ts currently includes z.null() in its top-level union, meaning it accepts null at runtime for any Json field — including required ones. However, the JsonValue type introduced in #2641 excludes null at the top level, so z.infer<> reports JsonValue while null still parses successfully, silently violating the TypeScript type.
Impact
- Required
Json fields: type says JsonValue (non-null), but runtime accepts null → silent type/runtime mismatch.
- Optional
Json? fields: benign — the ZodNullable wrapper already covers top-level null.
Proposed Fix
Split makeJsonSchema() into a base variant (no top-level z.null()) and a recursive item variant (JsonValue | null), used inside z.array(...) and z.object({}).catchall(...):
// factory.ts
private makeJsonItemSchema(): z.ZodType {
return z.union([this.makeJsonSchema(), z.null()]);
}
private makeJsonSchema(): z.ZodType<JsonValue> {
return z.union([
z.string(),
z.number(),
z.boolean(),
// z.null() removed — null at root is handled by nullable wrapper for Json? fields
z.array(z.lazy(() => this.makeJsonItemSchema())),
z.object({}).catchall(z.lazy(() => this.makeJsonItemSchema())),
]);
}
References
Requested by @ymc9.
Summary
makeJsonSchema()inpackages/zod/src/factory.tscurrently includesz.null()in its top-level union, meaning it acceptsnullat runtime for anyJsonfield — including required ones. However, theJsonValuetype introduced in #2641 excludesnullat the top level, soz.infer<>reportsJsonValuewhilenullstill parses successfully, silently violating the TypeScript type.Impact
Jsonfields: type saysJsonValue(non-null), but runtime acceptsnull→ silent type/runtime mismatch.Json?fields: benign — theZodNullablewrapper already covers top-levelnull.Proposed Fix
Split
makeJsonSchema()into a base variant (no top-levelz.null()) and a recursive item variant (JsonValue | null), used insidez.array(...)andz.object({}).catchall(...):References
JsonValue): fix(zod): json type compatibility between inferred zod types and @zenstackhq/orm types #2641Requested by @ymc9.