Skip to content

Commit 7ffedd0

Browse files
authored
Fix shape caching (#5263)
Fixes #5241
1 parent 923af80 commit 7ffedd0

File tree

5 files changed

+121
-39
lines changed

5 files changed

+121
-39
lines changed

packages/zod/src/v4/classic/schemas.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,14 @@ export const ZodType: core.$constructor<ZodType> = /*@__PURE__*/ core.$construct
146146
// base methods
147147
inst.check = (...checks) => {
148148
return inst.clone(
149-
{
150-
...def,
149+
util.mergeDefs(def, {
151150
checks: [
152151
...(def.checks ?? []),
153152
...checks.map((ch) =>
154153
typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" }, onattach: [] } } : ch
155154
),
156155
],
157-
}
158-
// { parent: true }
156+
})
159157
);
160158
};
161159
inst.clone = (def, params) => core.clone(inst, def, params);
@@ -1196,7 +1194,10 @@ export const ZodObject: core.$constructor<ZodObject> = /*@__PURE__*/ core.$const
11961194
core.$ZodObjectJIT.init(inst, def);
11971195
ZodType.init(inst, def);
11981196

1199-
util.defineLazy(inst, "shape", () => def.shape);
1197+
util.defineLazy(inst, "shape", () => {
1198+
return def.shape;
1199+
});
1200+
12001201
inst.keyof = () => _enum(Object.keys(inst._zod.def.shape)) as any;
12011202
inst.catchall = (catchall) => inst.clone({ ...inst._zod.def, catchall: catchall as any as core.$ZodType }) as any;
12021203
inst.passthrough = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
@@ -1223,10 +1224,7 @@ export function object<T extends core.$ZodLooseShape = Partial<Record<never, cor
12231224
): ZodObject<util.Writeable<T>, core.$strip> {
12241225
const def: core.$ZodObjectDef = {
12251226
type: "object",
1226-
get shape() {
1227-
util.assignProp(this, "shape", shape ? util.objectClone(shape) : {});
1228-
return this.shape;
1229-
},
1227+
shape: shape!,
12301228
...util.normalizeParams(params),
12311229
};
12321230
return new ZodObject(def) as any;
@@ -1240,10 +1238,7 @@ export function strictObject<T extends core.$ZodLooseShape>(
12401238
): ZodObject<T, core.$strict> {
12411239
return new ZodObject({
12421240
type: "object",
1243-
get shape() {
1244-
util.assignProp(this, "shape", util.objectClone(shape));
1245-
return this.shape;
1246-
},
1241+
shape,
12471242
catchall: never(),
12481243
...util.normalizeParams(params),
12491244
}) as any;
@@ -1257,10 +1252,7 @@ export function looseObject<T extends core.$ZodLooseShape>(
12571252
): ZodObject<T, core.$loose> {
12581253
return new ZodObject({
12591254
type: "object",
1260-
get shape() {
1261-
util.assignProp(this, "shape", util.objectClone(shape));
1262-
return this.shape;
1263-
},
1255+
shape,
12641256
catchall: unknown(),
12651257
...util.normalizeParams(params),
12661258
}) as any;

packages/zod/src/v4/classic/tests/recursive-types.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,3 +537,46 @@ export type RecursiveA = z.ZodUnion<
537537
}>,
538538
]
539539
>;
540+
541+
test("recursive type with `id` meta", () => {
542+
const AType = z.object({
543+
type: z.literal("a"),
544+
name: z.string(),
545+
});
546+
547+
const BType = z.object({
548+
type: z.literal("b"),
549+
name: z.string(),
550+
});
551+
552+
const CType = z.object({
553+
type: z.literal("c"),
554+
name: z.string(),
555+
});
556+
557+
const Schema = z.object({
558+
type: z.literal("special").meta({ description: "Type" }),
559+
config: z.object({
560+
title: z.string().meta({ description: "Title" }),
561+
get elements() {
562+
return z.array(z.discriminatedUnion("type", [AType, BType, CType])).meta({
563+
id: "SpecialElements",
564+
title: "SpecialElements",
565+
description: "Array of elements",
566+
});
567+
},
568+
}),
569+
});
570+
571+
Schema.parse({
572+
type: "special",
573+
config: {
574+
title: "Special",
575+
elements: [
576+
{ type: "a", name: "John" },
577+
{ type: "b", name: "Jane" },
578+
{ type: "c", name: "Jim" },
579+
],
580+
},
581+
});
582+
});

packages/zod/src/v4/core/schemas.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,7 +1739,7 @@ export interface $ZodObjectDef<Shape extends $ZodShape = $ZodShape> extends $Zod
17391739

17401740
export interface $ZodObjectInternals<
17411741
/** @ts-ignore Cast variance */
1742-
out Shape extends Readonly<$ZodShape> = Readonly<$ZodShape>,
1742+
out Shape extends $ZodShape = $ZodShape,
17431743
out Config extends $ZodObjectConfig = $ZodObjectConfig,
17441744
> extends _$ZodTypeInternals {
17451745
def: $ZodObjectDef<Shape>;
@@ -1825,6 +1825,21 @@ function handleCatchall(
18251825
export const $ZodObject: core.$constructor<$ZodObject> = /*@__PURE__*/ core.$constructor("$ZodObject", (inst, def) => {
18261826
// requires cast because technically $ZodObject doesn't extend
18271827
$ZodType.init(inst, def);
1828+
// const sh = def.shape;
1829+
const desc = Object.getOwnPropertyDescriptor(def, "shape");
1830+
if (!desc!.get) {
1831+
const sh = def.shape;
1832+
Object.defineProperty(def, "shape", {
1833+
get: () => {
1834+
const newSh = { ...sh };
1835+
Object.defineProperty(def, "shape", {
1836+
value: newSh,
1837+
});
1838+
1839+
return newSh;
1840+
},
1841+
});
1842+
}
18281843

18291844
const _normalized = util.cached(() => normalizeDef(def));
18301845

@@ -1853,7 +1868,6 @@ export const $ZodObject: core.$constructor<$ZodObject> = /*@__PURE__*/ core.$con
18531868
payload.issues.push({
18541869
expected: "object",
18551870
code: "invalid_type",
1856-
18571871
input,
18581872
inst,
18591873
});

packages/zod/src/v4/mini/schemas.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -769,10 +769,7 @@ export function object<T extends core.$ZodLooseShape = Record<never, SomeType>>(
769769
): ZodMiniObject<T, core.$strip> {
770770
const def: core.$ZodObjectDef = {
771771
type: "object",
772-
get shape() {
773-
util.assignProp(this, "shape", { ...shape });
774-
return this.shape;
775-
},
772+
shape: shape!,
776773
...util.normalizeParams(params),
777774
};
778775
return new ZodMiniObject(def) as any;
@@ -785,11 +782,7 @@ export function strictObject<T extends core.$ZodLooseShape>(
785782
): ZodMiniObject<T, core.$strict> {
786783
return new ZodMiniObject({
787784
type: "object",
788-
// shape: shape as core.$ZodLooseShape,
789-
get shape() {
790-
util.assignProp(this, "shape", { ...shape });
791-
return this.shape;
792-
},
785+
shape,
793786
catchall: never(),
794787
...util.normalizeParams(params),
795788
}) as any;
@@ -802,14 +795,7 @@ export function looseObject<T extends core.$ZodLooseShape>(
802795
): ZodMiniObject<T, core.$loose> {
803796
return new ZodMiniObject({
804797
type: "object",
805-
// shape: shape as core.$ZodLooseShape,
806-
get shape() {
807-
util.assignProp(this, "shape", { ...shape });
808-
return this.shape;
809-
},
810-
// get optional() {
811-
// return util.optionalKeys(shape);
812-
// },
798+
shape,
813799
catchall: unknown(),
814800
...util.normalizeParams(params),
815801
}) as any;

play.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,52 @@
1-
const test = () => 1;
1+
import * as z from "zod";
22

3-
test.something = () => 2;
3+
z;
44

5-
test.something;
5+
// export const LinesSchema = <T extends z.ZodType<unknown, any>>(schema: T) =>
6+
// z
7+
// .string()
8+
// .transform((input) => input.trim().split("\n"))
9+
// .pipe(z.array(schema));
10+
11+
const AType = z.object({
12+
type: z.literal("a"),
13+
name: z.string(),
14+
});
15+
16+
const BType = z.object({
17+
type: z.literal("b"),
18+
name: z.string(),
19+
});
20+
21+
const CType = z.object({
22+
type: z.literal("c"),
23+
name: z.string(),
24+
});
25+
26+
const Schema = z.object({
27+
type: z.literal("special").meta({ description: "Type" }),
28+
config: z.object({
29+
title: z.string().meta({ description: "Title" }),
30+
get elements() {
31+
return z.array(z.discriminatedUnion("type", [AType, BType, CType])).meta({
32+
id: "SpecialElements",
33+
title: "SpecialElements",
34+
description: "Array of elements",
35+
});
36+
},
37+
}),
38+
});
39+
40+
console.log(
41+
Schema.parse({
42+
type: "special",
43+
config: {
44+
title: "Special",
45+
elements: [
46+
{ type: "a", name: "John" },
47+
{ type: "b", name: "Jane" },
48+
{ type: "c", name: "Jim" },
49+
],
50+
},
51+
})
52+
);

0 commit comments

Comments
 (0)