zod 4.2.0-canary.20251118T062010 → 4.2.0-canary.20251118T185426
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/v4/classic/tests/index.test.ts +54 -0
- package/src/v4/classic/tests/record.test.ts +100 -9
- package/src/v4/core/schemas.ts +5 -3
- package/src/v4/core/tests/extend.test.ts +42 -1
- package/src/v4/core/tests/record-constructor.test.ts +67 -0
- package/src/v4/core/util.ts +2 -0
- package/src/v4/mini/tests/index.test.ts +23 -0
- package/v4/core/schemas.cjs +5 -3
- package/v4/core/schemas.js +5 -3
- package/v4/core/util.cjs +2 -0
- package/v4/core/util.js +2 -0
package/package.json
CHANGED
|
@@ -302,6 +302,29 @@ test("z.record", () => {
|
|
|
302
302
|
const d = z.record(z.enum(["a", "b"]).or(z.never()), z.string());
|
|
303
303
|
type d = z.output<typeof d>;
|
|
304
304
|
expectTypeOf<d>().toEqualTypeOf<Record<"a" | "b", string>>();
|
|
305
|
+
|
|
306
|
+
// literal union keys
|
|
307
|
+
const e = z.record(z.union([z.literal("a"), z.literal(0)]), z.string());
|
|
308
|
+
type e = z.output<typeof e>;
|
|
309
|
+
expectTypeOf<e>().toEqualTypeOf<Record<"a" | 0, string>>();
|
|
310
|
+
expect(z.parse(e, { a: "hello", 0: "world" })).toEqual({
|
|
311
|
+
a: "hello",
|
|
312
|
+
0: "world",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// TypeScript enum keys
|
|
316
|
+
enum Enum {
|
|
317
|
+
A = 0,
|
|
318
|
+
B = "hi",
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const f = z.record(z.enum(Enum), z.string());
|
|
322
|
+
type f = z.output<typeof f>;
|
|
323
|
+
expectTypeOf<f>().toEqualTypeOf<Record<Enum, string>>();
|
|
324
|
+
expect(z.parse(f, { [Enum.A]: "hello", [Enum.B]: "world" })).toEqual({
|
|
325
|
+
[Enum.A]: "hello",
|
|
326
|
+
[Enum.B]: "world",
|
|
327
|
+
});
|
|
305
328
|
});
|
|
306
329
|
|
|
307
330
|
test("z.map", () => {
|
|
@@ -786,6 +809,37 @@ test("isPlainObject", () => {
|
|
|
786
809
|
expect(z.core.util.isPlainObject("string")).toEqual(false);
|
|
787
810
|
expect(z.core.util.isPlainObject(123)).toEqual(false);
|
|
788
811
|
expect(z.core.util.isPlainObject(Symbol())).toEqual(false);
|
|
812
|
+
expect(z.core.util.isPlainObject({ constructor: "string" })).toEqual(true);
|
|
813
|
+
expect(z.core.util.isPlainObject({ constructor: 123 })).toEqual(true);
|
|
814
|
+
expect(z.core.util.isPlainObject({ constructor: null })).toEqual(true);
|
|
815
|
+
expect(z.core.util.isPlainObject({ constructor: undefined })).toEqual(true);
|
|
816
|
+
expect(z.core.util.isPlainObject({ constructor: true })).toEqual(true);
|
|
817
|
+
expect(z.core.util.isPlainObject({ constructor: {} })).toEqual(true);
|
|
818
|
+
expect(z.core.util.isPlainObject({ constructor: [] })).toEqual(true);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("shallowClone with constructor field", () => {
|
|
822
|
+
const objWithConstructor = { constructor: "string", key: "value" };
|
|
823
|
+
const cloned = z.core.util.shallowClone(objWithConstructor);
|
|
824
|
+
|
|
825
|
+
expect(cloned).toEqual(objWithConstructor);
|
|
826
|
+
expect(cloned).not.toBe(objWithConstructor);
|
|
827
|
+
expect(cloned.constructor).toBe("string");
|
|
828
|
+
expect(cloned.key).toBe("value");
|
|
829
|
+
|
|
830
|
+
const testCases = [
|
|
831
|
+
{ constructor: 123, data: "test" },
|
|
832
|
+
{ constructor: null, data: "test" },
|
|
833
|
+
{ constructor: true, data: "test" },
|
|
834
|
+
{ constructor: {}, data: "test" },
|
|
835
|
+
{ constructor: [], data: "test" },
|
|
836
|
+
];
|
|
837
|
+
|
|
838
|
+
for (const testCase of testCases) {
|
|
839
|
+
const clonedCase = z.core.util.shallowClone(testCase);
|
|
840
|
+
expect(clonedCase).toEqual(testCase);
|
|
841
|
+
expect(clonedCase).not.toBe(testCase);
|
|
842
|
+
}
|
|
789
843
|
});
|
|
790
844
|
|
|
791
845
|
test("def typing", () => {
|
|
@@ -8,16 +8,28 @@ test("type inference", () => {
|
|
|
8
8
|
const recordWithEnumKeys = z.record(z.enum(["Tuna", "Salmon"]), z.string());
|
|
9
9
|
type recordWithEnumKeys = z.infer<typeof recordWithEnumKeys>;
|
|
10
10
|
|
|
11
|
-
const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon"]), z.string());
|
|
11
|
+
const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
|
|
12
12
|
type recordWithLiteralKey = z.infer<typeof recordWithLiteralKey>;
|
|
13
13
|
|
|
14
|
-
const recordWithLiteralUnionKeys = z.record(
|
|
14
|
+
const recordWithLiteralUnionKeys = z.record(
|
|
15
|
+
z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]),
|
|
16
|
+
z.string()
|
|
17
|
+
);
|
|
15
18
|
type recordWithLiteralUnionKeys = z.infer<typeof recordWithLiteralUnionKeys>;
|
|
16
19
|
|
|
20
|
+
enum Enum {
|
|
21
|
+
Tuna = 0,
|
|
22
|
+
Salmon = "Shark",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const recordWithTypescriptEnum = z.record(z.enum(Enum), z.string());
|
|
26
|
+
type recordWithTypescriptEnum = z.infer<typeof recordWithTypescriptEnum>;
|
|
27
|
+
|
|
17
28
|
expectTypeOf<booleanRecord>().toEqualTypeOf<Record<string, boolean>>();
|
|
18
29
|
expectTypeOf<recordWithEnumKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
|
|
19
|
-
expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
|
|
20
|
-
expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
|
|
30
|
+
expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
|
|
31
|
+
expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
|
|
32
|
+
expectTypeOf<recordWithTypescriptEnum>().toEqualTypeOf<Record<Enum, string>>();
|
|
21
33
|
});
|
|
22
34
|
|
|
23
35
|
test("enum exhaustiveness", () => {
|
|
@@ -64,14 +76,76 @@ test("enum exhaustiveness", () => {
|
|
|
64
76
|
`);
|
|
65
77
|
});
|
|
66
78
|
|
|
79
|
+
test("typescript enum exhaustiveness", () => {
|
|
80
|
+
enum BigFish {
|
|
81
|
+
Tuna = 0,
|
|
82
|
+
Salmon = "Shark",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const schema = z.record(z.enum(BigFish), z.string());
|
|
86
|
+
const value = {
|
|
87
|
+
[BigFish.Tuna]: "asdf",
|
|
88
|
+
[BigFish.Salmon]: "asdf",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
expect(schema.parse(value)).toEqual(value);
|
|
92
|
+
|
|
93
|
+
expect(schema.safeParse({ [BigFish.Tuna]: "asdf", [BigFish.Salmon]: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
94
|
+
{
|
|
95
|
+
"error": [ZodError: [
|
|
96
|
+
{
|
|
97
|
+
"code": "unrecognized_keys",
|
|
98
|
+
"keys": [
|
|
99
|
+
"Trout"
|
|
100
|
+
],
|
|
101
|
+
"path": [],
|
|
102
|
+
"message": "Unrecognized key: \\"Trout\\""
|
|
103
|
+
}
|
|
104
|
+
]],
|
|
105
|
+
"success": false,
|
|
106
|
+
}
|
|
107
|
+
`);
|
|
108
|
+
expect(schema.safeParse({ [BigFish.Tuna]: "asdf" })).toMatchInlineSnapshot(`
|
|
109
|
+
{
|
|
110
|
+
"error": [ZodError: [
|
|
111
|
+
{
|
|
112
|
+
"expected": "string",
|
|
113
|
+
"code": "invalid_type",
|
|
114
|
+
"path": [
|
|
115
|
+
"Shark"
|
|
116
|
+
],
|
|
117
|
+
"message": "Invalid input: expected string, received undefined"
|
|
118
|
+
}
|
|
119
|
+
]],
|
|
120
|
+
"success": false,
|
|
121
|
+
}
|
|
122
|
+
`);
|
|
123
|
+
expect(schema.safeParse({ [BigFish.Salmon]: "asdf" })).toMatchInlineSnapshot(`
|
|
124
|
+
{
|
|
125
|
+
"error": [ZodError: [
|
|
126
|
+
{
|
|
127
|
+
"expected": "string",
|
|
128
|
+
"code": "invalid_type",
|
|
129
|
+
"path": [
|
|
130
|
+
0
|
|
131
|
+
],
|
|
132
|
+
"message": "Invalid input: expected string, received undefined"
|
|
133
|
+
}
|
|
134
|
+
]],
|
|
135
|
+
"success": false,
|
|
136
|
+
}
|
|
137
|
+
`);
|
|
138
|
+
});
|
|
139
|
+
|
|
67
140
|
test("literal exhaustiveness", () => {
|
|
68
|
-
const schema = z.record(z.literal(["Tuna", "Salmon"]), z.string());
|
|
141
|
+
const schema = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
|
|
69
142
|
schema.parse({
|
|
70
143
|
Tuna: "asdf",
|
|
71
144
|
Salmon: "asdf",
|
|
145
|
+
21: "asdf",
|
|
72
146
|
});
|
|
73
147
|
|
|
74
|
-
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
148
|
+
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
75
149
|
{
|
|
76
150
|
"error": [ZodError: [
|
|
77
151
|
{
|
|
@@ -96,6 +170,14 @@ test("literal exhaustiveness", () => {
|
|
|
96
170
|
"Salmon"
|
|
97
171
|
],
|
|
98
172
|
"message": "Invalid input: expected string, received undefined"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"expected": "string",
|
|
176
|
+
"code": "invalid_type",
|
|
177
|
+
"path": [
|
|
178
|
+
21
|
|
179
|
+
],
|
|
180
|
+
"message": "Invalid input: expected string, received undefined"
|
|
99
181
|
}
|
|
100
182
|
]],
|
|
101
183
|
"success": false,
|
|
@@ -143,13 +225,14 @@ test("pipe exhaustiveness", () => {
|
|
|
143
225
|
});
|
|
144
226
|
|
|
145
227
|
test("union exhaustiveness", () => {
|
|
146
|
-
const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon")]), z.string());
|
|
147
|
-
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf" })).toEqual({
|
|
228
|
+
const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]), z.string());
|
|
229
|
+
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf" })).toEqual({
|
|
148
230
|
Tuna: "asdf",
|
|
149
231
|
Salmon: "asdf",
|
|
232
|
+
21: "asdf",
|
|
150
233
|
});
|
|
151
234
|
|
|
152
|
-
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
235
|
+
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
153
236
|
{
|
|
154
237
|
"error": [ZodError: [
|
|
155
238
|
{
|
|
@@ -174,6 +257,14 @@ test("union exhaustiveness", () => {
|
|
|
174
257
|
"Salmon"
|
|
175
258
|
],
|
|
176
259
|
"message": "Invalid input: expected string, received undefined"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"expected": "string",
|
|
263
|
+
"code": "invalid_type",
|
|
264
|
+
"path": [
|
|
265
|
+
21
|
|
266
|
+
],
|
|
267
|
+
"message": "Invalid input: expected string, received undefined"
|
|
177
268
|
}
|
|
178
269
|
]],
|
|
179
270
|
"success": false,
|
package/src/v4/core/schemas.ts
CHANGED
|
@@ -2603,11 +2603,13 @@ export const $ZodRecord: core.$constructor<$ZodRecord> = /*@__PURE__*/ core.$con
|
|
|
2603
2603
|
|
|
2604
2604
|
const proms: Promise<any>[] = [];
|
|
2605
2605
|
|
|
2606
|
-
|
|
2607
|
-
|
|
2606
|
+
const values = def.keyType._zod.values;
|
|
2607
|
+
if (values) {
|
|
2608
2608
|
payload.value = {};
|
|
2609
|
+
const recordKeys = new Set<string | symbol>();
|
|
2609
2610
|
for (const key of values) {
|
|
2610
2611
|
if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
|
|
2612
|
+
recordKeys.add(typeof key === "number" ? key.toString() : key);
|
|
2611
2613
|
const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
|
|
2612
2614
|
|
|
2613
2615
|
if (result instanceof Promise) {
|
|
@@ -2630,7 +2632,7 @@ export const $ZodRecord: core.$constructor<$ZodRecord> = /*@__PURE__*/ core.$con
|
|
|
2630
2632
|
|
|
2631
2633
|
let unrecognized!: string[];
|
|
2632
2634
|
for (const key in input) {
|
|
2633
|
-
if (!
|
|
2635
|
+
if (!recordKeys.has(key)) {
|
|
2634
2636
|
unrecognized = unrecognized ?? [];
|
|
2635
2637
|
unrecognized.push(key);
|
|
2636
2638
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { test } from "vitest";
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
3
|
|
|
4
4
|
test("extend chaining preserves and overrides properties", () => {
|
|
@@ -16,3 +16,44 @@ test("extend chaining preserves and overrides properties", () => {
|
|
|
16
16
|
|
|
17
17
|
schema3.parse({ email: "test@example.com" });
|
|
18
18
|
});
|
|
19
|
+
|
|
20
|
+
test("extend with constructor field in shape", () => {
|
|
21
|
+
const baseSchema = z.object({
|
|
22
|
+
name: z.string(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const extendedSchema = baseSchema.extend({
|
|
26
|
+
constructor: z.string(),
|
|
27
|
+
age: z.number(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const result = extendedSchema.parse({
|
|
31
|
+
name: "John",
|
|
32
|
+
constructor: "Person",
|
|
33
|
+
age: 30,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
name: "John",
|
|
38
|
+
constructor: "Person",
|
|
39
|
+
age: 30,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const testCases = [
|
|
43
|
+
{ name: "Test", constructor: 123, age: 25 },
|
|
44
|
+
{ name: "Test", constructor: null, age: 25 },
|
|
45
|
+
{ name: "Test", constructor: true, age: 25 },
|
|
46
|
+
{ name: "Test", constructor: {}, age: 25 },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
for (const testCase of testCases) {
|
|
50
|
+
const anyConstructorSchema = baseSchema.extend({
|
|
51
|
+
constructor: z.any(),
|
|
52
|
+
age: z.number(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(() => anyConstructorSchema.parse(testCase)).not.toThrow();
|
|
56
|
+
const parsed = anyConstructorSchema.parse(testCase);
|
|
57
|
+
expect(parsed).toEqual(testCase);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
|
|
4
|
+
test("record should parse objects with non-function constructor field", () => {
|
|
5
|
+
const schema = z.record(z.string(), z.any());
|
|
6
|
+
|
|
7
|
+
expect(() => schema.parse({ constructor: "string", key: "value" })).not.toThrow();
|
|
8
|
+
|
|
9
|
+
const result1 = schema.parse({ constructor: "string", key: "value" });
|
|
10
|
+
expect(result1).toEqual({ constructor: "string", key: "value" });
|
|
11
|
+
|
|
12
|
+
expect(() => schema.parse({ constructor: 123, key: "value" })).not.toThrow();
|
|
13
|
+
|
|
14
|
+
const result2 = schema.parse({ constructor: 123, key: "value" });
|
|
15
|
+
expect(result2).toEqual({ constructor: 123, key: "value" });
|
|
16
|
+
|
|
17
|
+
expect(() => schema.parse({ constructor: null, key: "value" })).not.toThrow();
|
|
18
|
+
|
|
19
|
+
const result3 = schema.parse({ constructor: null, key: "value" });
|
|
20
|
+
expect(result3).toEqual({ constructor: null, key: "value" });
|
|
21
|
+
|
|
22
|
+
expect(() => schema.parse({ constructor: {}, key: "value" })).not.toThrow();
|
|
23
|
+
|
|
24
|
+
const result4 = schema.parse({ constructor: {}, key: "value" });
|
|
25
|
+
expect(result4).toEqual({ constructor: {}, key: "value" });
|
|
26
|
+
|
|
27
|
+
expect(() => schema.parse({ constructor: [], key: "value" })).not.toThrow();
|
|
28
|
+
|
|
29
|
+
const result5 = schema.parse({ constructor: [], key: "value" });
|
|
30
|
+
expect(result5).toEqual({ constructor: [], key: "value" });
|
|
31
|
+
|
|
32
|
+
expect(() => schema.parse({ constructor: true, key: "value" })).not.toThrow();
|
|
33
|
+
|
|
34
|
+
const result6 = schema.parse({ constructor: true, key: "value" });
|
|
35
|
+
expect(result6).toEqual({ constructor: true, key: "value" });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("record should still work with normal objects", () => {
|
|
39
|
+
const schema = z.record(z.string(), z.string());
|
|
40
|
+
|
|
41
|
+
expect(() => schema.parse({ normalKey: "value" })).not.toThrow();
|
|
42
|
+
|
|
43
|
+
const result1 = schema.parse({ normalKey: "value" });
|
|
44
|
+
expect(result1).toEqual({ normalKey: "value" });
|
|
45
|
+
|
|
46
|
+
expect(() => schema.parse({ key1: "value1", key2: "value2" })).not.toThrow();
|
|
47
|
+
|
|
48
|
+
const result2 = schema.parse({ key1: "value1", key2: "value2" });
|
|
49
|
+
expect(result2).toEqual({ key1: "value1", key2: "value2" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("record should validate values according to schema even with constructor field", () => {
|
|
53
|
+
const stringSchema = z.record(z.string(), z.string());
|
|
54
|
+
|
|
55
|
+
expect(() => stringSchema.parse({ constructor: "string", key: "value" })).not.toThrow();
|
|
56
|
+
|
|
57
|
+
expect(() => stringSchema.parse({ constructor: 123, key: "value" })).toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("record should work with different key types and constructor field", () => {
|
|
61
|
+
const enumSchema = z.record(z.enum(["constructor", "key"]), z.string());
|
|
62
|
+
|
|
63
|
+
expect(() => enumSchema.parse({ constructor: "value1", key: "value2" })).not.toThrow();
|
|
64
|
+
|
|
65
|
+
const result = enumSchema.parse({ constructor: "value1", key: "value2" });
|
|
66
|
+
expect(result).toEqual({ constructor: "value1", key: "value2" });
|
|
67
|
+
});
|
package/src/v4/core/util.ts
CHANGED
|
@@ -381,6 +381,8 @@ export function isPlainObject(o: any): o is Record<PropertyKey, unknown> {
|
|
|
381
381
|
const ctor = o.constructor;
|
|
382
382
|
if (ctor === undefined) return true;
|
|
383
383
|
|
|
384
|
+
if (typeof ctor !== "function") return true;
|
|
385
|
+
|
|
384
386
|
// modified prototype
|
|
385
387
|
const prot = ctor.prototype;
|
|
386
388
|
if (isObject(prot) === false) return false;
|
|
@@ -296,6 +296,29 @@ test("z.record", () => {
|
|
|
296
296
|
expect(() => z.parse(c, { a: "hello", b: "world" })).toThrow();
|
|
297
297
|
// extra keys
|
|
298
298
|
expect(() => z.parse(c, { a: "hello", b: "world", c: "world", d: "world" })).toThrow();
|
|
299
|
+
|
|
300
|
+
// literal union keys
|
|
301
|
+
const d = z.record(z.union([z.literal("a"), z.literal(0)]), z.string());
|
|
302
|
+
type d = z.output<typeof d>;
|
|
303
|
+
expectTypeOf<d>().toEqualTypeOf<Record<"a" | 0, string>>();
|
|
304
|
+
expect(z.parse(d, { a: "hello", 0: "world" })).toEqual({
|
|
305
|
+
a: "hello",
|
|
306
|
+
0: "world",
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// TypeScript enum keys
|
|
310
|
+
enum Enum {
|
|
311
|
+
A = 0,
|
|
312
|
+
B = "hi",
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const e = z.record(z.enum(Enum), z.string());
|
|
316
|
+
type e = z.output<typeof e>;
|
|
317
|
+
expectTypeOf<e>().toEqualTypeOf<Record<Enum, string>>();
|
|
318
|
+
expect(z.parse(e, { [Enum.A]: "hello", [Enum.B]: "world" })).toEqual({
|
|
319
|
+
[Enum.A]: "hello",
|
|
320
|
+
[Enum.B]: "world",
|
|
321
|
+
});
|
|
299
322
|
});
|
|
300
323
|
|
|
301
324
|
test("z.map", () => {
|
package/v4/core/schemas.cjs
CHANGED
|
@@ -1237,11 +1237,13 @@ exports.$ZodRecord = core.$constructor("$ZodRecord", (inst, def) => {
|
|
|
1237
1237
|
return payload;
|
|
1238
1238
|
}
|
|
1239
1239
|
const proms = [];
|
|
1240
|
-
|
|
1241
|
-
|
|
1240
|
+
const values = def.keyType._zod.values;
|
|
1241
|
+
if (values) {
|
|
1242
1242
|
payload.value = {};
|
|
1243
|
+
const recordKeys = new Set();
|
|
1243
1244
|
for (const key of values) {
|
|
1244
1245
|
if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
|
|
1246
|
+
recordKeys.add(typeof key === "number" ? key.toString() : key);
|
|
1245
1247
|
const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
|
|
1246
1248
|
if (result instanceof Promise) {
|
|
1247
1249
|
proms.push(result.then((result) => {
|
|
@@ -1261,7 +1263,7 @@ exports.$ZodRecord = core.$constructor("$ZodRecord", (inst, def) => {
|
|
|
1261
1263
|
}
|
|
1262
1264
|
let unrecognized;
|
|
1263
1265
|
for (const key in input) {
|
|
1264
|
-
if (!
|
|
1266
|
+
if (!recordKeys.has(key)) {
|
|
1265
1267
|
unrecognized = unrecognized ?? [];
|
|
1266
1268
|
unrecognized.push(key);
|
|
1267
1269
|
}
|
package/v4/core/schemas.js
CHANGED
|
@@ -1206,11 +1206,13 @@ export const $ZodRecord = /*@__PURE__*/ core.$constructor("$ZodRecord", (inst, d
|
|
|
1206
1206
|
return payload;
|
|
1207
1207
|
}
|
|
1208
1208
|
const proms = [];
|
|
1209
|
-
|
|
1210
|
-
|
|
1209
|
+
const values = def.keyType._zod.values;
|
|
1210
|
+
if (values) {
|
|
1211
1211
|
payload.value = {};
|
|
1212
|
+
const recordKeys = new Set();
|
|
1212
1213
|
for (const key of values) {
|
|
1213
1214
|
if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
|
|
1215
|
+
recordKeys.add(typeof key === "number" ? key.toString() : key);
|
|
1214
1216
|
const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
|
|
1215
1217
|
if (result instanceof Promise) {
|
|
1216
1218
|
proms.push(result.then((result) => {
|
|
@@ -1230,7 +1232,7 @@ export const $ZodRecord = /*@__PURE__*/ core.$constructor("$ZodRecord", (inst, d
|
|
|
1230
1232
|
}
|
|
1231
1233
|
let unrecognized;
|
|
1232
1234
|
for (const key in input) {
|
|
1233
|
-
if (!
|
|
1235
|
+
if (!recordKeys.has(key)) {
|
|
1234
1236
|
unrecognized = unrecognized ?? [];
|
|
1235
1237
|
unrecognized.push(key);
|
|
1236
1238
|
}
|
package/v4/core/util.cjs
CHANGED
package/v4/core/util.js
CHANGED
|
@@ -160,6 +160,8 @@ export function isPlainObject(o) {
|
|
|
160
160
|
const ctor = o.constructor;
|
|
161
161
|
if (ctor === undefined)
|
|
162
162
|
return true;
|
|
163
|
+
if (typeof ctor !== "function")
|
|
164
|
+
return true;
|
|
163
165
|
// modified prototype
|
|
164
166
|
const prot = ctor.prototype;
|
|
165
167
|
if (isObject(prot) === false)
|