zod 4.2.0-canary.20251118T062010 → 4.2.0-canary.20251118T063547

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod",
3
- "version": "4.2.0-canary.20251118T062010",
3
+ "version": "4.2.0-canary.20251118T063547",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -786,6 +786,37 @@ test("isPlainObject", () => {
786
786
  expect(z.core.util.isPlainObject("string")).toEqual(false);
787
787
  expect(z.core.util.isPlainObject(123)).toEqual(false);
788
788
  expect(z.core.util.isPlainObject(Symbol())).toEqual(false);
789
+ expect(z.core.util.isPlainObject({ constructor: "string" })).toEqual(true);
790
+ expect(z.core.util.isPlainObject({ constructor: 123 })).toEqual(true);
791
+ expect(z.core.util.isPlainObject({ constructor: null })).toEqual(true);
792
+ expect(z.core.util.isPlainObject({ constructor: undefined })).toEqual(true);
793
+ expect(z.core.util.isPlainObject({ constructor: true })).toEqual(true);
794
+ expect(z.core.util.isPlainObject({ constructor: {} })).toEqual(true);
795
+ expect(z.core.util.isPlainObject({ constructor: [] })).toEqual(true);
796
+ });
797
+
798
+ test("shallowClone with constructor field", () => {
799
+ const objWithConstructor = { constructor: "string", key: "value" };
800
+ const cloned = z.core.util.shallowClone(objWithConstructor);
801
+
802
+ expect(cloned).toEqual(objWithConstructor);
803
+ expect(cloned).not.toBe(objWithConstructor);
804
+ expect(cloned.constructor).toBe("string");
805
+ expect(cloned.key).toBe("value");
806
+
807
+ const testCases = [
808
+ { constructor: 123, data: "test" },
809
+ { constructor: null, data: "test" },
810
+ { constructor: true, data: "test" },
811
+ { constructor: {}, data: "test" },
812
+ { constructor: [], data: "test" },
813
+ ];
814
+
815
+ for (const testCase of testCases) {
816
+ const clonedCase = z.core.util.shallowClone(testCase);
817
+ expect(clonedCase).toEqual(testCase);
818
+ expect(clonedCase).not.toBe(testCase);
819
+ }
789
820
  });
790
821
 
791
822
  test("def typing", () => {
@@ -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
+ });
@@ -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;
package/v4/core/util.cjs CHANGED
@@ -215,6 +215,8 @@ function isPlainObject(o) {
215
215
  const ctor = o.constructor;
216
216
  if (ctor === undefined)
217
217
  return true;
218
+ if (typeof ctor !== "function")
219
+ return true;
218
220
  // modified prototype
219
221
  const prot = ctor.prototype;
220
222
  if (isObject(prot) === false)
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)