zod 4.2.0-canary.20251202T062120 → 4.2.0-canary.20251213T203150

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.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/src/v4/classic/schemas.ts +97 -5
  3. package/src/v4/classic/tests/fix-json-issue.test.ts +26 -0
  4. package/src/v4/classic/tests/json.test.ts +4 -3
  5. package/src/v4/classic/tests/standard-schema.test.ts +77 -0
  6. package/src/v4/classic/tests/to-json-schema-methods.test.ts +438 -0
  7. package/src/v4/classic/tests/to-json-schema.test.ts +66 -30
  8. package/src/v4/core/index.ts +2 -0
  9. package/src/v4/core/json-schema-generator.ts +124 -0
  10. package/src/v4/core/json-schema-processors.ts +630 -0
  11. package/src/v4/core/schemas.ts +8 -13
  12. package/src/v4/core/standard-schema.ts +114 -19
  13. package/src/v4/core/to-json-schema.ts +373 -827
  14. package/src/v4/mini/schemas.ts +2 -2
  15. package/src/v4/mini/tests/standard-schema.test.ts +17 -0
  16. package/v4/classic/schemas.cjs +48 -0
  17. package/v4/classic/schemas.d.cts +35 -0
  18. package/v4/classic/schemas.d.ts +35 -0
  19. package/v4/classic/schemas.js +48 -0
  20. package/v4/core/index.cjs +5 -1
  21. package/v4/core/index.d.cts +2 -0
  22. package/v4/core/index.d.ts +2 -0
  23. package/v4/core/index.js +2 -0
  24. package/v4/core/json-schema-generator.cjs +99 -0
  25. package/v4/core/json-schema-generator.d.cts +64 -0
  26. package/v4/core/json-schema-generator.d.ts +64 -0
  27. package/v4/core/json-schema-generator.js +95 -0
  28. package/v4/core/json-schema-processors.cjs +617 -0
  29. package/v4/core/json-schema-processors.d.cts +49 -0
  30. package/v4/core/json-schema-processors.d.ts +49 -0
  31. package/v4/core/json-schema-processors.js +574 -0
  32. package/v4/core/schemas.cjs +0 -10
  33. package/v4/core/schemas.d.cts +4 -1
  34. package/v4/core/schemas.d.ts +4 -1
  35. package/v4/core/schemas.js +0 -10
  36. package/v4/core/standard-schema.d.cts +90 -19
  37. package/v4/core/standard-schema.d.ts +90 -19
  38. package/v4/core/to-json-schema.cjs +302 -793
  39. package/v4/core/to-json-schema.d.cts +56 -33
  40. package/v4/core/to-json-schema.d.ts +56 -33
  41. package/v4/core/to-json-schema.js +296 -791
  42. package/v4/mini/schemas.d.cts +2 -2
  43. package/v4/mini/schemas.d.ts +2 -2
@@ -1,6 +1,6 @@
1
1
  import { Validator } from "@seriousme/openapi-schema-validator";
2
2
  import { describe, expect, test } from "vitest";
3
- import * as z from "zod/v4";
3
+ import * as z from "zod";
4
4
  // import * as zCore from "zod/v4/core";
5
5
 
6
6
  const openAPI30Validator = new Validator();
@@ -628,6 +628,42 @@ describe("toJSONSchema", () => {
628
628
  `);
629
629
  });
630
630
 
631
+ test("target normalization draft-04 and draft-07", () => {
632
+ // Test that both old (draft-4, draft-7) and new (draft-04, draft-07) target formats work
633
+ // Test draft-04 / draft-4
634
+ expect(z.toJSONSchema(z.number().gt(5), { target: "draft-04" })).toMatchInlineSnapshot(`
635
+ {
636
+ "$schema": "http://json-schema.org/draft-04/schema#",
637
+ "exclusiveMinimum": true,
638
+ "minimum": 5,
639
+ "type": "number",
640
+ }
641
+ `);
642
+ expect(z.toJSONSchema(z.number().gt(5), { target: "draft-4" })).toMatchInlineSnapshot(`
643
+ {
644
+ "$schema": "http://json-schema.org/draft-04/schema#",
645
+ "exclusiveMinimum": true,
646
+ "minimum": 5,
647
+ "type": "number",
648
+ }
649
+ `);
650
+ // Test draft-07 / draft-7
651
+ expect(z.toJSONSchema(z.number().gt(5), { target: "draft-07" })).toMatchInlineSnapshot(`
652
+ {
653
+ "$schema": "http://json-schema.org/draft-07/schema#",
654
+ "exclusiveMinimum": 5,
655
+ "type": "number",
656
+ }
657
+ `);
658
+ expect(z.toJSONSchema(z.number().gt(5), { target: "draft-7" })).toMatchInlineSnapshot(`
659
+ {
660
+ "$schema": "http://json-schema.org/draft-07/schema#",
661
+ "exclusiveMinimum": 5,
662
+ "type": "number",
663
+ }
664
+ `);
665
+ });
666
+
631
667
  test("nullable openapi-3.0", () => {
632
668
  const schema = z.string().nullable();
633
669
  const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
@@ -1577,7 +1613,7 @@ describe("toJSONSchema", () => {
1577
1613
  });
1578
1614
 
1579
1615
  test("override", () => {
1580
- const schema = z.z.toJSONSchema(z.string(), {
1616
+ const schema = z.toJSONSchema(z.string(), {
1581
1617
  override: (ctx) => {
1582
1618
  ctx.zodSchema;
1583
1619
  ctx.jsonSchema;
@@ -1603,7 +1639,7 @@ test("override: do not run on references", () => {
1603
1639
  .pipe(z.date())
1604
1640
  .meta({ c: true })
1605
1641
  .brand("dateIn");
1606
- z.z.toJSONSchema(schema, {
1642
+ z.toJSONSchema(schema, {
1607
1643
  unrepresentable: "any",
1608
1644
  io: "input",
1609
1645
  override(_) {
@@ -1616,7 +1652,7 @@ test("override: do not run on references", () => {
1616
1652
 
1617
1653
  test("override with refs", () => {
1618
1654
  const a = z.string().optional();
1619
- const result = z.z.toJSONSchema(a, {
1655
+ const result = z.toJSONSchema(a, {
1620
1656
  override(ctx) {
1621
1657
  if (ctx.zodSchema._zod.def.type === "string") {
1622
1658
  ctx.jsonSchema.type = "STRING" as "string";
@@ -1635,7 +1671,7 @@ test("override with refs", () => {
1635
1671
  test("override execution order", () => {
1636
1672
  const schema = z.union([z.string(), z.number()]);
1637
1673
  let unionSchema!: any;
1638
- z.z.toJSONSchema(schema, {
1674
+ z.toJSONSchema(schema, {
1639
1675
  override(ctx) {
1640
1676
  if (ctx.zodSchema._zod.def.type === "union") {
1641
1677
  unionSchema = ctx.jsonSchema;
@@ -1693,7 +1729,7 @@ test("pipe", () => {
1693
1729
  .pipe(z.number());
1694
1730
  // ZodPipe
1695
1731
 
1696
- const a = z.z.toJSONSchema(mySchema);
1732
+ const a = z.toJSONSchema(mySchema);
1697
1733
  expect(a).toMatchInlineSnapshot(`
1698
1734
  {
1699
1735
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1702,7 +1738,7 @@ test("pipe", () => {
1702
1738
  `);
1703
1739
  // => { type: "number" }
1704
1740
 
1705
- const b = z.z.toJSONSchema(mySchema, { io: "input" });
1741
+ const b = z.toJSONSchema(mySchema, { io: "input" });
1706
1742
  expect(b).toMatchInlineSnapshot(`
1707
1743
  {
1708
1744
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1727,7 +1763,7 @@ test("passthrough schemas", () => {
1727
1763
  e: z.pipe(Internal, Internal),
1728
1764
  });
1729
1765
 
1730
- const result = z.z.toJSONSchema(External, {
1766
+ const result = z.toJSONSchema(External, {
1731
1767
  reused: "ref",
1732
1768
  });
1733
1769
  expect(result).toMatchInlineSnapshot(`
@@ -1782,7 +1818,7 @@ test("passthrough schemas", () => {
1782
1818
 
1783
1819
  test("extract schemas with id", () => {
1784
1820
  const name = z.string().meta({ id: "name" });
1785
- const result = z.z.toJSONSchema(
1821
+ const result = z.toJSONSchema(
1786
1822
  z.object({
1787
1823
  first_name: name,
1788
1824
  last_name: name.nullable(),
@@ -1836,7 +1872,7 @@ test("extract schemas with id", () => {
1836
1872
  });
1837
1873
 
1838
1874
  test("unrepresentable literal values are ignored", () => {
1839
- const a = z.z.toJSONSchema(z.literal(["hello", null, 5, BigInt(1324), undefined]), { unrepresentable: "any" });
1875
+ const a = z.toJSONSchema(z.literal(["hello", null, 5, BigInt(1324), undefined]), { unrepresentable: "any" });
1840
1876
  expect(a).toMatchInlineSnapshot(`
1841
1877
  {
1842
1878
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1849,7 +1885,7 @@ test("unrepresentable literal values are ignored", () => {
1849
1885
  }
1850
1886
  `);
1851
1887
 
1852
- const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
1888
+ const b = z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
1853
1889
  unrepresentable: "any",
1854
1890
  });
1855
1891
  expect(b).toMatchInlineSnapshot(`
@@ -1863,7 +1899,7 @@ test("unrepresentable literal values are ignored", () => {
1863
1899
  }
1864
1900
  `);
1865
1901
 
1866
- const c = z.z.toJSONSchema(z.literal([undefined]), {
1902
+ const c = z.toJSONSchema(z.literal([undefined]), {
1867
1903
  unrepresentable: "any",
1868
1904
  });
1869
1905
  expect(c).toMatchInlineSnapshot(`
@@ -1876,7 +1912,7 @@ test("unrepresentable literal values are ignored", () => {
1876
1912
  test("describe with id", () => {
1877
1913
  const jobId = z.string().meta({ id: "jobId" });
1878
1914
 
1879
- const a = z.z.toJSONSchema(
1915
+ const a = z.toJSONSchema(
1880
1916
  z.object({
1881
1917
  current: jobId.describe("Current job"),
1882
1918
  previous: jobId.describe("Previous job"),
@@ -1914,7 +1950,7 @@ test("describe with id", () => {
1914
1950
  test("overwrite id", () => {
1915
1951
  const jobId = z.string().meta({ id: "aaa" });
1916
1952
 
1917
- const a = z.z.toJSONSchema(
1953
+ const a = z.toJSONSchema(
1918
1954
  z.object({
1919
1955
  current: jobId,
1920
1956
  previous: jobId.meta({ id: "bbb" }),
@@ -1950,7 +1986,7 @@ test("overwrite id", () => {
1950
1986
  }
1951
1987
  `);
1952
1988
 
1953
- const b = z.z.toJSONSchema(
1989
+ const b = z.toJSONSchema(
1954
1990
  z.object({
1955
1991
  current: jobId,
1956
1992
  previous: jobId.meta({ id: "ccc" }),
@@ -1993,7 +2029,7 @@ test("overwrite id", () => {
1993
2029
  test("overwrite descriptions", () => {
1994
2030
  const field = z.string().describe("a").describe("b").describe("c");
1995
2031
 
1996
- const a = z.z.toJSONSchema(
2032
+ const a = z.toJSONSchema(
1997
2033
  z.object({
1998
2034
  d: field.describe("d"),
1999
2035
  e: field.describe("e"),
@@ -2021,7 +2057,7 @@ test("overwrite descriptions", () => {
2021
2057
  }
2022
2058
  `);
2023
2059
 
2024
- const b = z.z.toJSONSchema(
2060
+ const b = z.toJSONSchema(
2025
2061
  z.object({
2026
2062
  d: field.describe("d"),
2027
2063
  e: field.describe("e"),
@@ -2084,7 +2120,7 @@ test("top-level readonly", () => {
2084
2120
  // z.globalRegistry.add(B, { id: "B" });
2085
2121
  // .meta({ id: "B" });
2086
2122
 
2087
- const result = z.z.toJSONSchema(A);
2123
+ const result = z.toJSONSchema(A);
2088
2124
  expect(result).toMatchInlineSnapshot(`
2089
2125
  {
2090
2126
  "$defs": {
@@ -2148,7 +2184,7 @@ test("basic registry", () => {
2148
2184
  myRegistry.add(User, { id: "User" });
2149
2185
  myRegistry.add(Post, { id: "Post" });
2150
2186
 
2151
- const result = z.z.toJSONSchema(myRegistry, {
2187
+ const result = z.toJSONSchema(myRegistry, {
2152
2188
  uri: (id) => `https://example.com/${id}.json`,
2153
2189
  });
2154
2190
  expect(result).toMatchInlineSnapshot(`
@@ -2204,7 +2240,7 @@ test("basic registry", () => {
2204
2240
 
2205
2241
  test("_ref", () => {
2206
2242
  // const a = z.promise(z.string().describe("a"));
2207
- const a = z.z.toJSONSchema(z.promise(z.string().describe("a")));
2243
+ const a = z.toJSONSchema(z.promise(z.string().describe("a")));
2208
2244
  expect(a).toMatchInlineSnapshot(`
2209
2245
  {
2210
2246
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -2213,7 +2249,7 @@ test("_ref", () => {
2213
2249
  }
2214
2250
  `);
2215
2251
 
2216
- const b = z.z.toJSONSchema(z.lazy(() => z.string().describe("a")));
2252
+ const b = z.toJSONSchema(z.lazy(() => z.string().describe("a")));
2217
2253
  expect(b).toMatchInlineSnapshot(`
2218
2254
  {
2219
2255
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -2222,7 +2258,7 @@ test("_ref", () => {
2222
2258
  }
2223
2259
  `);
2224
2260
 
2225
- const c = z.z.toJSONSchema(z.optional(z.string().describe("a")));
2261
+ const c = z.toJSONSchema(z.optional(z.string().describe("a")));
2226
2262
  expect(c).toMatchInlineSnapshot(`
2227
2263
  {
2228
2264
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -2441,7 +2477,7 @@ test("examples on pipe", () => {
2441
2477
  // .pipe(z.transform(Number).meta({ examples: [4] }))
2442
2478
  .meta({ examples: [4] });
2443
2479
 
2444
- const i = z.z.toJSONSchema(schema, { io: "input", unrepresentable: "any" });
2480
+ const i = z.toJSONSchema(schema, { io: "input", unrepresentable: "any" });
2445
2481
  expect(i).toMatchInlineSnapshot(`
2446
2482
  {
2447
2483
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -2451,7 +2487,7 @@ test("examples on pipe", () => {
2451
2487
  "type": "string",
2452
2488
  }
2453
2489
  `);
2454
- const o = z.z.toJSONSchema(schema, { io: "output", unrepresentable: "any" });
2490
+ const o = z.toJSONSchema(schema, { io: "output", unrepresentable: "any" });
2455
2491
  expect(o).toMatchInlineSnapshot(`
2456
2492
  {
2457
2493
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -2463,21 +2499,21 @@ test("examples on pipe", () => {
2463
2499
  });
2464
2500
 
2465
2501
  // test("number checks", () => {
2466
- // expect(z.z.toJSONSchema(z.number().int())).toMatchInlineSnapshot(`
2502
+ // expect(z.toJSONSchema(z.number().int())).toMatchInlineSnapshot(`
2467
2503
  // {
2468
2504
  // "maximum": 9007199254740991,
2469
2505
  // "minimum": -9007199254740991,
2470
2506
  // "type": "integer",
2471
2507
  // }
2472
2508
  // `);
2473
- // expect(z.z.toJSONSchema(z.int())).toMatchInlineSnapshot(`
2509
+ // expect(z.toJSONSchema(z.int())).toMatchInlineSnapshot(`
2474
2510
  // {
2475
2511
  // "maximum": 9007199254740991,
2476
2512
  // "minimum": -9007199254740991,
2477
2513
  // "type": "integer",
2478
2514
  // }
2479
2515
  // `);
2480
- // expect(z.z.toJSONSchema(z.int().positive())).toMatchInlineSnapshot(`
2516
+ // expect(z.toJSONSchema(z.int().positive())).toMatchInlineSnapshot(`
2481
2517
  // {
2482
2518
  // "exclusiveMinimum": 0,
2483
2519
  // "maximum": 9007199254740991,
@@ -2485,14 +2521,14 @@ test("examples on pipe", () => {
2485
2521
  // "type": "integer",
2486
2522
  // }
2487
2523
  // `);
2488
- // expect(z.z.toJSONSchema(z.int().nonnegative())).toMatchInlineSnapshot(`
2524
+ // expect(z.toJSONSchema(z.int().nonnegative())).toMatchInlineSnapshot(`
2489
2525
  // {
2490
2526
  // "maximum": 9007199254740991,
2491
2527
  // "minimum": 0,
2492
2528
  // "type": "integer",
2493
2529
  // }
2494
2530
  // `);
2495
- // expect(z.z.toJSONSchema(z.int().gt(0))).toMatchInlineSnapshot(`
2531
+ // expect(z.toJSONSchema(z.int().gt(0))).toMatchInlineSnapshot(`
2496
2532
  // {
2497
2533
  // "exclusiveMinimum": 0,
2498
2534
  // "maximum": 9007199254740991,
@@ -2500,7 +2536,7 @@ test("examples on pipe", () => {
2500
2536
  // "type": "integer",
2501
2537
  // }
2502
2538
  // `);
2503
- // expect(z.z.toJSONSchema(z.int().gte(0))).toMatchInlineSnapshot(`
2539
+ // expect(z.toJSONSchema(z.int().gte(0))).toMatchInlineSnapshot(`
2504
2540
  // {
2505
2541
  // "maximum": 9007199254740991,
2506
2542
  // "minimum": 0,
@@ -11,4 +11,6 @@ export * from "./registries.js";
11
11
  export * from "./doc.js";
12
12
  export * from "./api.js";
13
13
  export * from "./to-json-schema.js";
14
+ export { toJSONSchema } from "./json-schema-processors.js";
15
+ export { JSONSchemaGenerator } from "./json-schema-generator.js";
14
16
  export * as JSONSchema from "./json-schema.js";
@@ -0,0 +1,124 @@
1
+ import { allProcessors } from "./json-schema-processors.js";
2
+ import type * as JSONSchema from "./json-schema.js";
3
+ import type * as schemas from "./schemas.js";
4
+ import {
5
+ type JSONSchemaGeneratorParams,
6
+ type ProcessParams,
7
+ type ToJSONSchemaContext,
8
+ extractDefs,
9
+ finalize,
10
+ initializeContext,
11
+ process,
12
+ } from "./to-json-schema.js";
13
+
14
+ /**
15
+ * Parameters for the emit method of JSONSchemaGenerator.
16
+ * @deprecated Use toJSONSchema function instead
17
+ */
18
+ export type EmitParams = Pick<JSONSchemaGeneratorParams, "cycles" | "reused" | "external">;
19
+
20
+ /**
21
+ * Parameters for JSONSchemaGenerator constructor.
22
+ * @deprecated Use toJSONSchema function instead
23
+ */
24
+ type JSONSchemaGeneratorConstructorParams = Pick<
25
+ JSONSchemaGeneratorParams,
26
+ "metadata" | "target" | "unrepresentable" | "override" | "io"
27
+ >;
28
+
29
+ /**
30
+ * Legacy class-based interface for JSON Schema generation.
31
+ * This class wraps the new functional implementation to provide backward compatibility.
32
+ *
33
+ * @deprecated Use the `toJSONSchema` function instead for new code.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Legacy usage (still supported)
38
+ * const gen = new JSONSchemaGenerator({ target: "draft-07" });
39
+ * gen.process(schema);
40
+ * const result = gen.emit(schema);
41
+ *
42
+ * // Preferred modern usage
43
+ * const result = toJSONSchema(schema, { target: "draft-07" });
44
+ * ```
45
+ */
46
+ export class JSONSchemaGenerator {
47
+ private ctx: ToJSONSchemaContext;
48
+
49
+ /** @deprecated Access via ctx instead */
50
+ get metadataRegistry() {
51
+ return this.ctx.metadataRegistry;
52
+ }
53
+ /** @deprecated Access via ctx instead */
54
+ get target() {
55
+ return this.ctx.target;
56
+ }
57
+ /** @deprecated Access via ctx instead */
58
+ get unrepresentable() {
59
+ return this.ctx.unrepresentable;
60
+ }
61
+ /** @deprecated Access via ctx instead */
62
+ get override() {
63
+ return this.ctx.override;
64
+ }
65
+ /** @deprecated Access via ctx instead */
66
+ get io() {
67
+ return this.ctx.io;
68
+ }
69
+ /** @deprecated Access via ctx instead */
70
+ get counter() {
71
+ return this.ctx.counter;
72
+ }
73
+ set counter(value: number) {
74
+ this.ctx.counter = value;
75
+ }
76
+ /** @deprecated Access via ctx instead */
77
+ get seen() {
78
+ return this.ctx.seen;
79
+ }
80
+
81
+ constructor(params?: JSONSchemaGeneratorConstructorParams) {
82
+ // Normalize target for internal context
83
+ let normalizedTarget: ToJSONSchemaContext["target"] = params?.target ?? "draft-2020-12";
84
+ if (normalizedTarget === "draft-4") normalizedTarget = "draft-04";
85
+ if (normalizedTarget === "draft-7") normalizedTarget = "draft-07";
86
+
87
+ this.ctx = initializeContext({
88
+ processors: allProcessors,
89
+ target: normalizedTarget,
90
+ ...(params?.metadata && { metadata: params.metadata }),
91
+ ...(params?.unrepresentable && { unrepresentable: params.unrepresentable }),
92
+ ...(params?.override && { override: params.override as any }),
93
+ ...(params?.io && { io: params.io }),
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Process a schema to prepare it for JSON Schema generation.
99
+ * This must be called before emit().
100
+ */
101
+ process(schema: schemas.$ZodType, _params: ProcessParams = { path: [], schemaPath: [] }): JSONSchema.BaseSchema {
102
+ return process(schema, this.ctx, _params);
103
+ }
104
+
105
+ /**
106
+ * Emit the final JSON Schema after processing.
107
+ * Must call process() first.
108
+ */
109
+ emit(schema: schemas.$ZodType, _params?: EmitParams): JSONSchema.BaseSchema {
110
+ // Apply emit params to the context
111
+ if (_params) {
112
+ if (_params.cycles) this.ctx.cycles = _params.cycles;
113
+ if (_params.reused) this.ctx.reused = _params.reused;
114
+ if (_params.external) this.ctx.external = _params.external;
115
+ }
116
+
117
+ extractDefs(this.ctx, schema);
118
+ const result = finalize(this.ctx, schema);
119
+
120
+ // Strip ~standard property to match old implementation's return type
121
+ const { "~standard": _, ...plainResult } = result as any;
122
+ return plainResult as JSONSchema.BaseSchema;
123
+ }
124
+ }