/
opt
/
canhelp
/
node_modules
/
better-auth
/
node_modules
/
zod
/
src
/
v4
/
classic
/
tests
/
/opt/canhelp/node_modules/better-auth/node_modules/zod/src/v4/classic/tests
mkdir
upload
Name
Size
Mode
Actions
anyunknown.test.ts
639
0644
edit
dl
rm
apply.test.ts
1509
0644
edit
dl
rm
array.test.ts
7248
0644
edit
dl
rm
assignability.test.ts
6790
0644
edit
dl
rm
async-parsing.test.ts
12311
0644
edit
dl
rm
async-refinements.test.ts
1867
0644
edit
dl
rm
base.test.ts
179
0644
edit
dl
rm
bigint.test.ts
1956
0644
edit
dl
rm
brand.test.ts
3574
0644
edit
dl
rm
catch.test.ts
7831
0644
edit
dl
rm
coalesce.test.ts
743
0644
edit
dl
rm
codec-examples.test.ts
19030
0644
edit
dl
rm
codec.test.ts
18853
0644
edit
dl
rm
coerce.test.ts
7736
0644
edit
dl
rm
continuability.test.ts
8020
0644
edit
dl
rm
custom.test.ts
1215
0644
edit
dl
rm
date.test.ts
1671
0644
edit
dl
rm
datetime.test.ts
11932
0644
edit
dl
rm
default.test.ts
9226
0644
edit
dl
rm
describe-meta-checks.test.ts
965
0644
edit
dl
rm
description.test.ts
1294
0644
edit
dl
rm
discriminated-unions.test.ts
17665
0644
edit
dl
rm
enum.test.ts
8488
0644
edit
dl
rm
error-utils.test.ts
13744
0644
edit
dl
rm
error.test.ts
19502
0644
edit
dl
rm
file.test.ts
2641
0644
edit
dl
rm
firstparty.test.ts
3045
0644
edit
dl
rm
fix-json-issue.test.ts
771
0644
edit
dl
rm
from-json-schema.test.ts
21498
0644
edit
dl
rm
function.test.ts
8344
0644
edit
dl
rm
generics.test.ts
2162
0644
edit
dl
rm
hash.test.ts
3204
0644
edit
dl
rm
index.test.ts
31191
0644
edit
dl
rm
instanceof.test.ts
1658
0644
edit
dl
rm
intersection.test.ts
5555
0644
edit
dl
rm
json.test.ts
3327
0644
edit
dl
rm
lazy.test.ts
4756
0644
edit
dl
rm
literal.test.ts
3095
0644
edit
dl
rm
map.test.ts
7622
0644
edit
dl
rm
nan.test.ts
645
0644
edit
dl
rm
nested-refine.test.ts
3623
0644
edit
dl
rm
nonoptional.test.ts
2841
0644
edit
dl
rm
nullable.test.ts
591
0644
edit
dl
rm
number.test.ts
8644
0644
edit
dl
rm
object.test.ts
18481
0644
edit
dl
rm
optional.test.ts
7332
0644
edit
dl
rm
partial.test.ts
12973
0644
edit
dl
rm
pickomit.test.ts
6885
0644
edit
dl
rm
pipe.test.ts
2338
0644
edit
dl
rm
prefault.test.ts
2063
0644
edit
dl
rm
preprocess.test.ts
6522
0644
edit
dl
rm
primitive.test.ts
7649
0644
edit
dl
rm
promise.test.ts
2324
0644
edit
dl
rm
prototypes.test.ts
498
0644
edit
dl
rm
readonly.test.ts
11786
0644
edit
dl
rm
record.test.ts
17226
0644
edit
dl
rm
recursive-types.test.ts
12693
0644
edit
dl
rm
refine.test.ts
15649
0644
edit
dl
rm
registries.test.ts
7391
0644
edit
dl
rm
set.test.ts
5622
0644
edit
dl
rm
standard-schema.test.ts
3770
0644
edit
dl
rm
string-formats.test.ts
3700
0644
edit
dl
rm
string.test.ts
78069
0644
edit
dl
rm
stringbool.test.ts
3536
0644
edit
dl
rm
template-literal.test.ts
35894
0644
edit
dl
rm
to-json-schema-methods.test.ts
10100
0644
edit
dl
rm
to-json-schema.test.ts
78639
0644
edit
dl
rm
transform.test.ts
8298
0644
edit
dl
rm
tuple.test.ts
5532
0644
edit
dl
rm
union.test.ts
5936
0644
edit
dl
rm
url.test.ts
402
0644
edit
dl
rm
validations.test.ts
6736
0644
edit
dl
rm
void.test.ts
306
0644
edit
dl
rm
Edit:
/opt/canhelp/node_modules/better-auth/node_modules/zod/src/v4/classic/tests/to-json-schema.test.ts
(78639B)
import { Validator } from "@seriousme/openapi-schema-validator"; import { describe, expect, test } from "vitest"; import * as z from "zod"; // import * as zCore from "zod/v4/core"; const openAPI30Validator = new Validator(); /** @see https://github.com/colinhacks/zod/issues/5147 */ const validateOpenAPI30Schema = async (zodJSONSchema: Record<string, unknown>): Promise<true> => { const res = await openAPI30Validator.validate({ openapi: "3.0.0", info: { title: "SampleApi", description: "Sample backend service", version: "1.0.0", }, components: { schemas: { test: zodJSONSchema } }, paths: {}, }); if (!res.valid) { // `console.error` should make `vitest` trow an unhandled error // printing the validation messages in consoles console.error( `OpenAPI schema is not valid against ${openAPI30Validator.version}`, JSON.stringify(res.errors, null, 2) ); } return true; }; describe("toJSONSchema", () => { test("primitive types", () => { expect(z.toJSONSchema(z.string())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); expect(z.toJSONSchema(z.number())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "number", } `); expect(z.toJSONSchema(z.boolean())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "boolean", } `); expect(z.toJSONSchema(z.null())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "null", } `); expect(z.toJSONSchema(z.undefined(), { unrepresentable: "any" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", } `); expect(z.toJSONSchema(z.any())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", } `); expect(z.toJSONSchema(z.unknown())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", } `); expect(z.toJSONSchema(z.never())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "not": {}, } `); expect(z.toJSONSchema(z.email())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$", "type": "string", } `); expect(z.toJSONSchema(z.iso.datetime())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "date-time", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "type": "string", } `); expect(z.toJSONSchema(z.iso.date())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "date", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$", "type": "string", } `); expect(z.toJSONSchema(z.iso.time())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$", "type": "string", } `); expect(z.toJSONSchema(z.iso.time({ precision: -1 }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$", "type": "string", } `); expect(z.toJSONSchema(z.iso.time({ precision: 0 }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d$", "type": "string", } `); expect(z.toJSONSchema(z.iso.time({ precision: 3 }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}$", "type": "string", } `); expect(z.toJSONSchema(z.iso.duration())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "duration", "pattern": "^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$", "type": "string", } `); expect(z.toJSONSchema(z.ipv4())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "ipv4", "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$", "type": "string", } `); expect(z.toJSONSchema(z.ipv6())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "ipv6", "pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$", "type": "string", } `); expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "mac", "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$", "type": "string", } `); expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "mac", "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$", "type": "string", } `); expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "mac", "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$", "type": "string", } `); expect(z.toJSONSchema(z.uuid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string", } `); expect(z.toJSONSchema(z.guid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", "type": "string", } `); expect(z.toJSONSchema(z.url())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "uri", "type": "string", } `); expect(z.toJSONSchema(z.base64())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "contentEncoding": "base64", "format": "base64", "pattern": "^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$", "type": "string", } `); expect(z.toJSONSchema(z.cuid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "cuid", "pattern": "^[cC][^\\s-]{8,}$", "type": "string", } `); // expect(z.toJSONSchema(z.regex(/asdf/))).toMatchInlineSnapshot(); expect(z.toJSONSchema(z.emoji())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "emoji", "pattern": "^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$", "type": "string", } `); expect(z.toJSONSchema(z.nanoid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "nanoid", "pattern": "^[a-zA-Z0-9_-]{21}$", "type": "string", } `); expect(z.toJSONSchema(z.cuid2())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "cuid2", "pattern": "^[0-9a-z]+$", "type": "string", } `); expect(z.toJSONSchema(z.ulid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "ulid", "pattern": "^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$", "type": "string", } `); // expect(z.toJSONSchema(z.cidr())).toMatchInlineSnapshot(); expect(z.toJSONSchema(z.number())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "number", } `); expect(z.toJSONSchema(z.int())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 9007199254740991, "minimum": -9007199254740991, "type": "integer", } `); expect(z.toJSONSchema(z.int32())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 2147483647, "minimum": -2147483648, "type": "integer", } `); expect(z.toJSONSchema(z.float32())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 3.4028234663852886e+38, "minimum": -3.4028234663852886e+38, "type": "number", } `); expect(z.toJSONSchema(z.float64())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 1.7976931348623157e+308, "minimum": -1.7976931348623157e+308, "type": "number", } `); expect(z.toJSONSchema(z.jwt())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "jwt", "type": "string", } `); }); test("unsupported schema types", () => { expect(() => z.toJSONSchema(z.bigint())).toThrow("BigInt cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.int64())).toThrow("BigInt cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.symbol())).toThrow("Symbols cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.void())).toThrow("Void cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.undefined())).toThrow("Undefined cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.date())).toThrow("Date cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.map(z.string(), z.number()))).toThrow("Map cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.set(z.string()))).toThrow("Set cannot be represented in JSON Schema"); expect(() => z.toJSONSchema(z.custom(() => true))).toThrow("Custom types cannot be represented in JSON Schema"); // Transform const transformSchema = z.string().transform((val) => Number.parseInt(val)); expect(() => z.toJSONSchema(transformSchema)).toThrow("Transforms cannot be represented in JSON Schema"); // Static catch values const staticCatchSchema = z.string().catch(() => "sup"); expect(z.toJSONSchema(staticCatchSchema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "default": "sup", "type": "string", } `); // Dynamic catch values const dynamicCatchSchema = z.string().catch((ctx) => `${ctx.issues.length}`); expect(() => z.toJSONSchema(dynamicCatchSchema)).toThrow("Dynamic catch values are not supported in JSON Schema"); }); test("string formats", () => { expect(z.toJSONSchema(z.string().email())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$", "type": "string", } `); expect(z.toJSONSchema(z.string().uuid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", "type": "string", } `); expect(z.toJSONSchema(z.iso.datetime())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "date-time", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "type": "string", } `); expect(z.toJSONSchema(z.iso.date())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "date", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$", "type": "string", } `); expect(z.toJSONSchema(z.iso.time())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$", "type": "string", } `); expect(z.toJSONSchema(z.iso.duration())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "duration", "pattern": "^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$", "type": "string", } `); // expect(z.toJSONSchema(z.string().ip())).toMatchInlineSnapshot(` // { // "pattern": /\\(\\^\\(\\?:\\(\\?:25\\[0-5\\]\\|2\\[0-4\\]\\[0-9\\]\\|1\\[0-9\\]\\[0-9\\]\\|\\[1-9\\]\\[0-9\\]\\|\\[0-9\\]\\)\\\\\\.\\)\\{3\\}\\(\\?:25\\[0-5\\]\\|2\\[0-4\\]\\[0-9\\]\\|1\\[0-9\\]\\[0-9\\]\\|\\[1-9\\]\\[0-9\\]\\|\\[0-9\\]\\)\\$\\)\\|\\(\\^\\(\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{7\\}\\|::\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,6\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{1\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,5\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{2\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,4\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{3\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,3\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{4\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,2\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{5\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,1\\}\\)\\(\\[a-fA-F0-9\\]\\{1,4\\}\\|\\(\\(\\(25\\[0-5\\]\\)\\|\\(2\\[0-4\\]\\[0-9\\]\\)\\|\\(1\\[0-9\\]\\{2\\}\\)\\|\\(\\[0-9\\]\\{1,2\\}\\)\\)\\\\\\.\\)\\{3\\}\\(\\(25\\[0-5\\]\\)\\|\\(2\\[0-4\\]\\[0-9\\]\\)\\|\\(1\\[0-9\\]\\{2\\}\\)\\|\\(\\[0-9\\]\\{1,2\\}\\)\\)\\)\\$\\)/, // "type": "string", // } // `); expect(z.toJSONSchema(z.ipv4())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "ipv4", "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$", "type": "string", } `); expect(z.toJSONSchema(z.ipv6())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "ipv6", "pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$", "type": "string", } `); expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "mac", "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$", "type": "string", } `); expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "mac", "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$", "type": "string", } `); expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "mac", "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$", "type": "string", } `); expect(z.toJSONSchema(z.base64())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "contentEncoding": "base64", "format": "base64", "pattern": "^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$", "type": "string", } `); expect(z.toJSONSchema(z.url())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "uri", "type": "string", } `); expect(z.toJSONSchema(z.guid())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", "type": "string", } `); expect(z.toJSONSchema(z.string().regex(/asdf/))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "pattern": "asdf", "type": "string", } `); }); test("string patterns", () => { expect( z.toJSONSchema( z .string() .startsWith("hello") .includes("cruel") .includes("dark", { position: 10 }) .endsWith("world") .regex(/stuff/) ) ).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "allOf": [ { "pattern": "^hello.*", }, { "pattern": "cruel", }, { "pattern": "^.{10}dark", }, { "pattern": ".*world$", }, { "pattern": "stuff", }, ], "type": "string", } `); expect( z.toJSONSchema( z .string() .startsWith("hello") .includes("cruel") .includes("dark", { position: 10 }) .endsWith("world") .regex(/stuff/), { target: "draft-7", } ) ).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "allOf": [ { "pattern": "^hello.*", "type": "string", }, { "pattern": "cruel", "type": "string", }, { "pattern": "^.{10}dark", "type": "string", }, { "pattern": ".*world$", "type": "string", }, { "pattern": "stuff", "type": "string", }, ], "type": "string", } `); }); test("number constraints", () => { expect(z.toJSONSchema(z.number().min(5).max(10))).toMatchInlineSnapshot( ` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 10, "minimum": 5, "type": "number", } ` ); expect(z.toJSONSchema(z.number().gt(5).gt(10))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "exclusiveMinimum": 10, "type": "number", } `); expect(z.toJSONSchema(z.number().gt(5).gte(10))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "minimum": 10, "type": "number", } `); expect(z.toJSONSchema(z.number().lt(5).lt(3))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "exclusiveMaximum": 3, "type": "number", } `); expect(z.toJSONSchema(z.number().lt(5).lt(3).lte(2))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 2, "type": "number", } `); expect(z.toJSONSchema(z.number().lt(5).lte(3))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 3, "type": "number", } `); expect(z.toJSONSchema(z.number().gt(5).lt(10))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "exclusiveMaximum": 10, "exclusiveMinimum": 5, "type": "number", } `); expect(z.toJSONSchema(z.number().gte(5).lte(10))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 10, "minimum": 5, "type": "number", } `); expect(z.toJSONSchema(z.number().positive())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "exclusiveMinimum": 0, "type": "number", } `); expect(z.toJSONSchema(z.number().negative())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "exclusiveMaximum": 0, "type": "number", } `); expect(z.toJSONSchema(z.number().nonpositive())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "maximum": 0, "type": "number", } `); expect(z.toJSONSchema(z.number().nonnegative())).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "minimum": 0, "type": "number", } `); }); test("number constraints draft-4", () => { expect(z.toJSONSchema(z.number().gt(5).lt(10), { target: "draft-4" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-04/schema#", "exclusiveMaximum": true, "exclusiveMinimum": true, "maximum": 10, "minimum": 5, "type": "number", } `); }); test("target normalization draft-04 and draft-07", () => { // Test that both old (draft-4, draft-7) and new (draft-04, draft-07) target formats work // Test draft-04 / draft-4 expect(z.toJSONSchema(z.number().gt(5), { target: "draft-04" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-04/schema#", "exclusiveMinimum": true, "minimum": 5, "type": "number", } `); expect(z.toJSONSchema(z.number().gt(5), { target: "draft-4" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-04/schema#", "exclusiveMinimum": true, "minimum": 5, "type": "number", } `); // Test draft-07 / draft-7 expect(z.toJSONSchema(z.number().gt(5), { target: "draft-07" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "exclusiveMinimum": 5, "type": "number", } `); expect(z.toJSONSchema(z.number().gt(5), { target: "draft-7" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "exclusiveMinimum": 5, "type": "number", } `); }); test("nullable openapi-3.0", () => { const schema = z.string().nullable(); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "nullable": true, "type": "string", } `); }); test("union with null openapi-3.0", () => { const schema = z.union([z.string(), z.null()]); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "anyOf": [ { "type": "string", }, { "enum": [ null, ], "nullable": true, "type": "string", }, ], } `); }); test("number with exclusive min-max openapi-3.0", () => { const schema = z.number().lt(100).gt(1); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "exclusiveMaximum": true, "exclusiveMinimum": true, "maximum": 100, "minimum": 1, "type": "number", } `); }); test("arrays", () => { expect(z.toJSONSchema(z.array(z.string()))).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "items": { "type": "string", }, "type": "array", } `); }); test("unions", () => { const schema = z.union([z.string(), z.number()]); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "anyOf": [ { "type": "string", }, { "type": "number", }, ], } `); }); test("discriminated unions", () => { const schema = z.discriminatedUnion("type", [ z.object({ type: z.literal("success"), data: z.string() }), z.object({ type: z.literal("error"), message: z.string() }), ]); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "oneOf": [ { "additionalProperties": false, "properties": { "data": { "type": "string", }, "type": { "const": "success", "type": "string", }, }, "required": [ "type", "data", ], "type": "object", }, { "additionalProperties": false, "properties": { "message": { "type": "string", }, "type": { "const": "error", "type": "string", }, }, "required": [ "type", "message", ], "type": "object", }, ], } `); }); test("intersections", () => { const schema = z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() })); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "allOf": [ { "additionalProperties": false, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", }, { "additionalProperties": false, "properties": { "age": { "type": "number", }, }, "required": [ "age", ], "type": "object", }, ], } `); }); test("record", () => { const schema = z.record(z.string(), z.boolean()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "boolean", }, "propertyNames": { "type": "string", }, "type": "object", } `); }); test("record openapi-3.0", () => { const schema = z.record(z.string(), z.boolean()); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "additionalProperties": { "type": "boolean", }, "type": "object", } `); }); test("record with enum keys adds required", () => { const schema = z.record(z.enum(["key1", "key2"]), z.number()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "number", }, "propertyNames": { "enum": [ "key1", "key2", ], "type": "string", }, "required": [ "key1", "key2", ], "type": "object", } `); }); test("record filters enum values to strings and numbers for required", () => { enum NumberEnum { Zero = 0, One = 1, } const schema = z.record(z.enum(NumberEnum), z.string()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "string", }, "propertyNames": { "enum": [ 0, 1, ], "type": "number", }, "required": [ 0, 1, ], "type": "object", } `); }); test("strict record with regex key uses propertyNames", () => { const schema = z.record(z.string().regex(/^label:[a-z]{2}$/), z.string()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "string", }, "propertyNames": { "pattern": "^label:[a-z]{2}$", "type": "string", }, "type": "object", } `); }); test("looseRecord with regex key uses patternProperties", () => { const schema = z.looseRecord(z.string().regex(/^label:[a-z]{2}$/), z.string()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "patternProperties": { "^label:[a-z]{2}$": { "type": "string", }, }, "type": "object", } `); }); test("looseRecord with multiple regex patterns uses patternProperties", () => { const schema = z.looseRecord( z .string() .regex(/^prefix_/) .regex(/_suffix$/), z.number() ); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "patternProperties": { "^prefix_": { "type": "number", }, "_suffix$": { "type": "number", }, }, "type": "object", } `); }); test("looseRecord without regex key uses propertyNames", () => { // looseRecord with plain string key should still use propertyNames const schema = z.looseRecord(z.string(), z.boolean()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "boolean", }, "propertyNames": { "type": "string", }, "type": "object", } `); }); test("intersection of object with looseRecord uses patternProperties", () => { const zLabeled = z.object({ label: z.string() }); const zLocalizedLabeled = z.looseRecord(z.string().regex(/^label:[a-z]{2}$/), z.string()); const schema = zLabeled.and(zLocalizedLabeled); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "allOf": [ { "additionalProperties": false, "properties": { "label": { "type": "string", }, }, "required": [ "label", ], "type": "object", }, { "patternProperties": { "^label:[a-z]{2}$": { "type": "string", }, }, "type": "object", }, ], } `); }); test("tuple", () => { const schema = z.tuple([z.string(), z.number()]); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "prefixItems": [ { "type": "string", }, { "type": "number", }, ], "type": "array", } `); }); test("tuple with rest", () => { const schema = z.tuple([z.string(), z.number()]).rest(z.boolean()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "items": { "type": "boolean", }, "prefixItems": [ { "type": "string", }, { "type": "number", }, ], "type": "array", } `); }); test("tuple openapi-3.0", () => { const schema = z.tuple([z.string(), z.number()]); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "items": { "anyOf": [ { "type": "string", }, { "type": "number", }, ], }, "maxItems": 2, "minItems": 2, "type": "array", } `); }); test("tuple with rest openapi-3.0", () => { const schema = z.tuple([z.string(), z.number()]).rest(z.boolean()); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "items": { "anyOf": [ { "type": "string", }, { "type": "number", }, { "type": "boolean", }, ], }, "minItems": 3, "type": "array", } `); }); test("tuple with null openapi-3.0", () => { const schema = z.tuple([z.string(), z.number(), z.null()]); const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" }); validateOpenAPI30Schema(jsonSchema); expect(jsonSchema).toMatchInlineSnapshot(` { "items": { "anyOf": [ { "type": "string", }, { "type": "number", }, { "enum": [ null, ], "nullable": true, "type": "string", }, ], }, "maxItems": 3, "minItems": 3, "type": "array", } `); }); test("tuple draft-7", () => { const schema = z.tuple([z.string(), z.number()]); expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "items": [ { "type": "string", }, { "type": "number", }, ], "type": "array", } `); }); test("tuple with rest draft-7", () => { const schema = z.tuple([z.string(), z.number()]).rest(z.boolean()); expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "additionalItems": { "type": "boolean", }, "items": [ { "type": "string", }, { "type": "number", }, ], "type": "array", } `); }); test("tuple with rest draft-7 - issue #5151 regression test", () => { // This test addresses issue #5151: tuple with rest elements and ids // in draft-7 had incorrect internal path handling affecting complex scenarios const primarySchema = z.string().meta({ id: "primary" }); const restSchema = z.number().meta({ id: "rest" }); const testSchema = z.tuple([primarySchema], restSchema); // Test both final output structure AND internal path handling const capturedPaths: string[] = []; const result = z.toJSONSchema(testSchema, { target: "draft-7", override: (ctx) => capturedPaths.push(ctx.path.join("/")), }); // Verify correct draft-7 structure with metadata extraction expect(result).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "additionalItems": { "$ref": "#/definitions/rest", }, "definitions": { "primary": { "id": "primary", "type": "string", }, "rest": { "id": "rest", "type": "number", }, }, "items": [ { "$ref": "#/definitions/primary", }, ], "type": "array", } `); // Verify internal paths are correct (this was the actual bug) expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths // Structural validations expect(Array.isArray(result.items)).toBe(true); expect(result.additionalItems).toBeDefined(); }); test("promise", () => { const schema = z.promise(z.string()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); }); test("lazy", () => { const schema = z.lazy(() => z.string()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); }); // enum test("enum", () => { const a = z.enum(["a", "b", "c"]); expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "enum": [ "a", "b", "c", ], "type": "string", } `); enum B { A = 0, B = 1, C = 2, } const b = z.enum(B); expect(z.toJSONSchema(b)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "enum": [ 0, 1, 2, ], "type": "number", } `); }); // literal test("literal", () => { const a = z.literal("hello"); expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "const": "hello", "type": "string", } `); const b = z.literal(7); expect(z.toJSONSchema(b)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "const": 7, "type": "number", } `); const c = z.literal(["hello", undefined, null, 5, BigInt(1324)]); expect(() => z.toJSONSchema(c)).toThrow(); const d = z.literal(["hello", null, 5]); expect(z.toJSONSchema(d)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "enum": [ "hello", null, 5, ], } `); const e = z.literal(["hello", "zod", "v4"]); expect(z.toJSONSchema(e)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "enum": [ "hello", "zod", "v4", ], "type": "string", } `); }); test("literal draft-4", () => { const a = z.literal("hello"); expect(z.toJSONSchema(a, { target: "draft-4" })).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-04/schema#", "enum": [ "hello", ], "type": "string", } `); }); // pipe test("pipe", () => { const schema = z .string() .transform((val) => Number.parseInt(val)) .pipe(z.number()); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "number", } `); }); test("simple objects", () => { const schema = z.object({ name: z.string(), age: z.number(), }); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot( ` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "age": { "type": "number", }, "name": { "type": "string", }, }, "required": [ "name", "age", ], "type": "object", } ` ); }); test("additionalproperties in z.object", () => { const a = z.object({ name: z.string(), }); expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); expect( z.toJSONSchema(a, { io: "input", override(ctx) { const def = ctx.zodSchema._zod.def; if (def.type === "object" && !def.catchall) { (ctx.jsonSchema as z.core.JSONSchema.ObjectSchema).additionalProperties = false; } }, }) ).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); }); test("catchall objects", () => { const a = z.strictObject({ name: z.string(), age: z.number(), }); expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "age": { "type": "number", }, "name": { "type": "string", }, }, "required": [ "name", "age", ], "type": "object", } `); const b = z .object({ name: z.string(), }) .catchall(z.string()); expect(z.toJSONSchema(b)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "string", }, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); const c = z.looseObject({ name: z.string(), }); expect(z.toJSONSchema(c)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); }); test("optional fields - object", () => { const schema = z.object({ required: z.string(), optional: z.string().optional(), nonoptional: z.string().optional().nonoptional(), }); const result = z.toJSONSchema(schema); expect(result).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "nonoptional": { "type": "string", }, "optional": { "type": "string", }, "required": { "type": "string", }, }, "required": [ "required", "nonoptional", ], "type": "object", } `); }); test("recursive object", () => { interface Category { name: string; subcategories: Category[]; } const categorySchema: z.ZodType<Category> = z.object({ name: z.string(), subcategories: z.array(z.lazy(() => categorySchema)), }); const result = z.toJSONSchema(categorySchema); expect(result).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "name": { "type": "string", }, "subcategories": { "items": { "$ref": "#", }, "type": "array", }, }, "required": [ "name", "subcategories", ], "type": "object", } `); }); test("simple interface", () => { const userSchema = z.object({ name: z.string(), age: z.number().optional(), }); const result = z.toJSONSchema(userSchema); expect(result).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "age": { "type": "number", }, "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); }); test("catchall interface", () => { const a = z.strictObject({ name: z.string(), age: z.number(), }); expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "age": { "type": "number", }, "name": { "type": "string", }, }, "required": [ "name", "age", ], "type": "object", } `); const b = z .object({ name: z.string(), }) .catchall(z.string()); expect(z.toJSONSchema(b)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": { "type": "string", }, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); const c = z.looseObject({ name: z.string(), }); expect(z.toJSONSchema(c)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": {}, "properties": { "name": { "type": "string", }, }, "required": [ "name", ], "type": "object", } `); }); test("recursive interface schemas", () => { const TreeNodeSchema = z.object({ id: z.string(), get children() { return TreeNodeSchema; }, }); const result = z.toJSONSchema(TreeNodeSchema); // Should have definitions for recursive schema expect(JSON.stringify(result, null, 2)).toMatchInlineSnapshot( ` "{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "id": { "type": "string" }, "children": { "$ref": "#" } }, "required": [ "id", "children" ], "additionalProperties": false }" ` ); }); test("mutually recursive interface schemas", () => { const FolderSchema = z.object({ name: z.string(), get files() { return z.array(FileSchema); }, }); const FileSchema = z.object({ name: z.string(), get parent() { return FolderSchema; }, }); const result = z.toJSONSchema(FolderSchema); // Should have definitions for both schemas expect(JSON.stringify(result, null, 2)).toMatchInlineSnapshot( ` "{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "name": { "type": "string" }, "files": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "parent": { "$ref": "#" } }, "required": [ "name", "parent" ], "additionalProperties": false } } }, "required": [ "name", "files" ], "additionalProperties": false }" ` ); }); }); test("override", () => { const schema = z.toJSONSchema(z.string(), { override: (ctx) => { ctx.zodSchema; ctx.jsonSchema; ctx.jsonSchema.whatever = "sup"; }, }); expect(schema).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", "whatever": "sup", } `); }); test("override: do not run on references", () => { let overrideCount = 0; const schema = z .union([z.string().date(), z.string().datetime(), z.string().datetime({ local: true })]) .meta({ a: true }) .transform((str) => new Date(str)) .meta({ b: true }) .pipe(z.date()) .meta({ c: true }) .brand("dateIn"); z.toJSONSchema(schema, { unrepresentable: "any", io: "input", override(_) { overrideCount++; }, }); expect(overrideCount).toBe(12); }); test("override with refs", () => { const a = z.string().optional(); const result = z.toJSONSchema(a, { override(ctx) { if (ctx.zodSchema._zod.def.type === "string") { ctx.jsonSchema.type = "STRING" as "string"; } }, }); expect(result).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "STRING", } `); }); test("override execution order", () => { const schema = z.union([z.string(), z.number()]); let unionSchema!: any; z.toJSONSchema(schema, { override(ctx) { if (ctx.zodSchema._zod.def.type === "union") { unionSchema = ctx.jsonSchema; } }, }); expect(unionSchema).toMatchInlineSnapshot(` { "anyOf": [ { "type": "string", }, { "type": "number", }, ], } `); }); test("override with path", () => { const userSchema = z.object({ name: z.string(), age: z.number(), }); const capturedPaths: (string | number)[][] = []; z.toJSONSchema(userSchema, { override(ctx) { capturedPaths.push(ctx.path); }, }); expect(capturedPaths).toMatchInlineSnapshot(` [ [ "properties", "age", ], [ "properties", "name", ], [], ] `); }); test("pipe", () => { const mySchema = z .string() .transform((val) => val.length) .pipe(z.number()); // ZodPipe const a = z.toJSONSchema(mySchema); expect(a).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "number", } `); // => { type: "number" } const b = z.toJSONSchema(mySchema, { io: "input" }); expect(b).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); // => { type: "string" } }); test("passthrough schemas", () => { const Internal = z.object({ num: z.number(), str: z.string(), }); //.meta({ id: "Internal" }); const External = z.object({ a: Internal, b: Internal.optional(), c: z.lazy(() => Internal), d: z.promise(Internal), e: z.pipe(Internal, Internal), }); const result = z.toJSONSchema(External, { reused: "ref", }); expect(result).toMatchInlineSnapshot(` { "$defs": { "__schema0": { "additionalProperties": false, "properties": { "num": { "type": "number", }, "str": { "type": "string", }, }, "required": [ "num", "str", ], "type": "object", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "a": { "$ref": "#/$defs/__schema0", }, "b": { "$ref": "#/$defs/__schema0", }, "c": { "$ref": "#/$defs/__schema0", }, "d": { "$ref": "#/$defs/__schema0", }, "e": { "$ref": "#/$defs/__schema0", }, }, "required": [ "a", "c", "d", "e", ], "type": "object", } `); }); test("extract schemas with id", () => { const name = z.string().meta({ id: "name" }); const result = z.toJSONSchema( z.object({ first_name: name, last_name: name.nullable(), middle_name: name.optional(), age: z.number().meta({ id: "age" }), }) ); expect(result).toMatchInlineSnapshot(` { "$defs": { "age": { "id": "age", "type": "number", }, "name": { "id": "name", "type": "string", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "age": { "$ref": "#/$defs/age", }, "first_name": { "$ref": "#/$defs/name", }, "last_name": { "anyOf": [ { "$ref": "#/$defs/name", }, { "type": "null", }, ], }, "middle_name": { "$ref": "#/$defs/name", }, }, "required": [ "first_name", "last_name", "age", ], "type": "object", } `); }); test("unrepresentable literal values are ignored", () => { const a = z.toJSONSchema(z.literal(["hello", null, 5, BigInt(1324), undefined]), { unrepresentable: "any" }); expect(a).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "enum": [ "hello", null, 5, 1324, ], } `); const b = z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), { unrepresentable: "any", }); expect(b).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "enum": [ null, 5, 1324, ], } `); const c = z.toJSONSchema(z.literal([undefined]), { unrepresentable: "any", }); expect(c).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", } `); }); test("describe with id", () => { const jobId = z.string().meta({ id: "jobId" }); const a = z.toJSONSchema( z.object({ current: jobId.describe("Current job"), previous: jobId.describe("Previous job"), }) ); expect(a).toMatchInlineSnapshot(` { "$defs": { "jobId": { "id": "jobId", "type": "string", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "current": { "$ref": "#/$defs/jobId", "description": "Current job", }, "previous": { "$ref": "#/$defs/jobId", "description": "Previous job", }, }, "required": [ "current", "previous", ], "type": "object", } `); }); test("describe with id on wrapper", () => { // Test that $ref propagation works when processor sets a different ref (readonly -> innerType) // but parent was extracted due to having an id const roJobId = z.string().readonly().meta({ id: "roJobId" }); const a = z.toJSONSchema( z.object({ current: roJobId.describe("Current readonly job"), previous: roJobId.describe("Previous readonly job"), }) ); expect(a).toMatchInlineSnapshot(` { "$defs": { "roJobId": { "id": "roJobId", "readOnly": true, "type": "string", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "current": { "$ref": "#/$defs/roJobId", "description": "Current readonly job", }, "previous": { "$ref": "#/$defs/roJobId", "description": "Previous readonly job", }, }, "required": [ "current", "previous", ], "type": "object", } `); }); test("overwrite id", () => { const jobId = z.string().meta({ id: "aaa" }); const a = z.toJSONSchema( z.object({ current: jobId, previous: jobId.meta({ id: "bbb" }), }) ); expect(a).toMatchInlineSnapshot(` { "$defs": { "aaa": { "id": "aaa", "type": "string", }, "bbb": { "$ref": "#/$defs/aaa", "id": "bbb", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "current": { "$ref": "#/$defs/aaa", }, "previous": { "$ref": "#/$defs/bbb", }, }, "required": [ "current", "previous", ], "type": "object", } `); const b = z.toJSONSchema( z.object({ current: jobId, previous: jobId.meta({ id: "ccc" }), }), { reused: "ref", } ); expect(b).toMatchInlineSnapshot(` { "$defs": { "aaa": { "id": "aaa", "type": "string", }, "ccc": { "$ref": "#/$defs/aaa", "id": "ccc", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "current": { "$ref": "#/$defs/aaa", }, "previous": { "$ref": "#/$defs/ccc", }, }, "required": [ "current", "previous", ], "type": "object", } `); }); test("overwrite descriptions", () => { const field = z.string().describe("a").describe("b").describe("c"); const a = z.toJSONSchema( z.object({ d: field.describe("d"), e: field.describe("e"), }) ); expect(a).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "d": { "description": "d", "type": "string", }, "e": { "description": "e", "type": "string", }, }, "required": [ "d", "e", ], "type": "object", } `); const b = z.toJSONSchema( z.object({ d: field.describe("d"), e: field.describe("e"), }), { reused: "ref", } ); expect(b).toMatchInlineSnapshot(` { "$defs": { "__schema0": { "description": "c", "type": "string", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "d": { "$ref": "#/$defs/__schema0", "description": "d", }, "e": { "$ref": "#/$defs/__schema0", "description": "e", }, }, "required": [ "d", "e", ], "type": "object", } `); }); test("top-level readonly", () => { const A = z .object({ name: z.string(), get b() { return B; }, }) .readonly() .meta({ id: "A" }); // z.globalRegistry.add(A, { id: "A" }); // .meta({ id: "A" }); const B = z .object({ name: z.string(), get a() { return A; }, }) .readonly() .meta({ id: "B" }); // z.globalRegistry.add(B, { id: "B" }); // .meta({ id: "B" }); const result = z.toJSONSchema(A); expect(result).toMatchInlineSnapshot(` { "$defs": { "B": { "additionalProperties": false, "id": "B", "properties": { "a": { "$ref": "#", }, "name": { "type": "string", }, }, "readOnly": true, "required": [ "name", "a", ], "type": "object", }, }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "id": "A", "properties": { "b": { "$ref": "#/$defs/B", }, "name": { "type": "string", }, }, "readOnly": true, "required": [ "name", "b", ], "type": "object", } `); }); test("basic registry", () => { const myRegistry = z.registry<{ id: string }>(); const User = z.object({ name: z.string(), get posts() { return z.array(Post); }, }); const Post = z.object({ title: z.string(), content: z.string(), get author() { return User; }, }); myRegistry.add(User, { id: "User" }); myRegistry.add(Post, { id: "Post" }); const result = z.toJSONSchema(myRegistry, { uri: (id) => `https://example.com/${id}.json`, }); expect(result).toMatchInlineSnapshot(` { "schemas": { "Post": { "$id": "https://example.com/Post.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "author": { "$ref": "https://example.com/User.json", }, "content": { "type": "string", }, "title": { "type": "string", }, }, "required": [ "title", "content", "author", ], "type": "object", }, "User": { "$id": "https://example.com/User.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "name": { "type": "string", }, "posts": { "items": { "$ref": "https://example.com/Post.json", }, "type": "array", }, }, "required": [ "name", "posts", ], "type": "object", }, }, } `); }); test("_ref", () => { // const a = z.promise(z.string().describe("a")); const a = z.toJSONSchema(z.promise(z.string().describe("a"))); expect(a).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "a", "type": "string", } `); const b = z.toJSONSchema(z.lazy(() => z.string().describe("a"))); expect(b).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "a", "type": "string", } `); const c = z.toJSONSchema(z.optional(z.string().describe("a"))); expect(c).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "a", "type": "string", } `); const d = z.toJSONSchema(z.string().meta({ id: "foo" }).describe("bar").optional()); expect(d).toMatchInlineSnapshot(` { "$defs": { "foo": { "id": "foo", "type": "string", }, }, "$ref": "#/$defs/foo", "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "bar", } `); }); test("defaults/prefaults", () => { const a = z .string() .transform((val) => val.length) .pipe(z.number()); const b = a.prefault("hello"); const c = a.default(1234); // a expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "number", } `); expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); // b expect(z.toJSONSchema(b)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "number", } `); expect(z.toJSONSchema(b, { io: "input" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "default": "hello", "type": "string", } `); // c expect(z.toJSONSchema(c)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "default": 1234, "type": "number", } `); expect(z.toJSONSchema(c, { io: "input" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); }); test("input type", () => { const schema = z.object({ a: z.string(), b: z.string().optional(), c: z.string().default("hello"), d: z.string().nullable(), e: z.string().prefault("hello"), f: z.string().catch("hello"), g: z.never(), h: z.union([z.string(), z.number().default(2)]), i: z.union([z.string(), z.string().optional()]), }); expect(z.toJSONSchema(schema, { io: "input" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "a": { "type": "string", }, "b": { "type": "string", }, "c": { "default": "hello", "type": "string", }, "d": { "anyOf": [ { "type": "string", }, { "type": "null", }, ], }, "e": { "default": "hello", "type": "string", }, "f": { "default": "hello", "type": "string", }, "g": { "not": {}, }, "h": { "anyOf": [ { "type": "string", }, { "default": 2, "type": "number", }, ], }, "i": { "anyOf": [ { "type": "string", }, { "type": "string", }, ], }, }, "required": [ "a", "d", "f", "g", ], "type": "object", } `); expect(z.toJSONSchema(schema, { io: "output" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "a": { "type": "string", }, "b": { "type": "string", }, "c": { "default": "hello", "type": "string", }, "d": { "anyOf": [ { "type": "string", }, { "type": "null", }, ], }, "e": { "type": "string", }, "f": { "default": "hello", "type": "string", }, "g": { "not": {}, }, "h": { "anyOf": [ { "type": "string", }, { "default": 2, "type": "number", }, ], }, "i": { "anyOf": [ { "type": "string", }, { "type": "string", }, ], }, }, "required": [ "a", "c", "d", "e", "f", "g", "h", ], "type": "object", } `); }); test("examples on pipe", () => { const schema = z .string() .meta({ examples: ["test"] }) .transform(Number) // .pipe(z.transform(Number).meta({ examples: [4] })) .meta({ examples: [4] }); const i = z.toJSONSchema(schema, { io: "input", unrepresentable: "any" }); expect(i).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "examples": [ "test", ], "type": "string", } `); const o = z.toJSONSchema(schema, { io: "output", unrepresentable: "any" }); expect(o).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "examples": [ 4, ], } `); }); // test("number checks", () => { // expect(z.toJSONSchema(z.number().int())).toMatchInlineSnapshot(` // { // "maximum": 9007199254740991, // "minimum": -9007199254740991, // "type": "integer", // } // `); // expect(z.toJSONSchema(z.int())).toMatchInlineSnapshot(` // { // "maximum": 9007199254740991, // "minimum": -9007199254740991, // "type": "integer", // } // `); // expect(z.toJSONSchema(z.int().positive())).toMatchInlineSnapshot(` // { // "exclusiveMinimum": 0, // "maximum": 9007199254740991, // "minimum": -9007199254740991, // "type": "integer", // } // `); // expect(z.toJSONSchema(z.int().nonnegative())).toMatchInlineSnapshot(` // { // "maximum": 9007199254740991, // "minimum": 0, // "type": "integer", // } // `); // expect(z.toJSONSchema(z.int().gt(0))).toMatchInlineSnapshot(` // { // "exclusiveMinimum": 0, // "maximum": 9007199254740991, // "minimum": -9007199254740991, // "type": "integer", // } // `); // expect(z.toJSONSchema(z.int().gte(0))).toMatchInlineSnapshot(` // { // "maximum": 9007199254740991, // "minimum": 0, // "type": "integer", // } // `); // }); test("use output type for preprocess", () => { const a = z.preprocess((val) => String(val), z.string()); expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string", } `); }); // test("isTransforming", () => { // const tx = z.core.isTransforming; // expect(tx(z.string())).toEqual(false); // expect(tx(z.string().transform((val) => val))).toEqual(true); // expect(tx(z.string().pipe(z.string()))).toEqual(false); // expect( // tx( // z // .string() // .transform((val) => val) // .pipe(z.string()) // ) // ).toEqual(true); // const a = z.transform((val) => val); // expect(tx(z.transform((val) => val))).toEqual(true); // expect(tx(a.optional())).toEqual(true); // const b = z.string().optional(); // expect(tx(b)).toEqual(false); // const c = z.string().prefault("hello"); // expect(tx(c)).toEqual(false); // const d = z.string().default("hello"); // expect(tx(d)).toEqual(false); // }); test("flatten simple intersections", () => { const FirstSchema = z.object({ testNum: z.number(), }); const SecondSchema = z.object({ testStr: z.string(), }); const ThirdSchema = z.object({ testBool: z.boolean(), }); const HelloSchema = FirstSchema.and(SecondSchema).and(ThirdSchema).describe("123"); // Zod 3 // console.log(JSON.stringify(zodToJsonSchema(HelloSchema), null, 2)); // Zod 4 const result = z.toJSONSchema(HelloSchema, { target: "draft-7" }); expect(result).toMatchInlineSnapshot(` { "$schema": "http://json-schema.org/draft-07/schema#", "allOf": [ { "additionalProperties": false, "properties": { "testNum": { "type": "number", }, }, "required": [ "testNum", ], "type": "object", }, { "additionalProperties": false, "properties": { "testStr": { "type": "string", }, }, "required": [ "testStr", ], "type": "object", }, { "additionalProperties": false, "properties": { "testBool": { "type": "boolean", }, }, "required": [ "testBool", ], "type": "object", }, ], "description": "123", } `); }); test("z.file()", () => { const a = z.file(); expect(z.toJSONSchema(a)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "contentEncoding": "binary", "format": "binary", "type": "string", } `); const b = z.file().mime("image/png").min(1000).max(10000); expect(z.toJSONSchema(b)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "contentEncoding": "binary", "contentMediaType": "image/png", "format": "binary", "maxLength": 10000, "minLength": 1000, "type": "string", } `); const c = z.file().mime(["image/png", "image/jpg"]).min(1000).max(10000); expect(z.toJSONSchema(c)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "anyOf": [ { "contentMediaType": "image/png", }, { "contentMediaType": "image/jpg", }, ], "contentEncoding": "binary", "format": "binary", "maxLength": 10000, "minLength": 1000, "type": "string", } `); }); test("custom toJSONSchema", () => { const schema = z.instanceof(Date); schema._zod.toJSONSchema = () => ({ type: "string", format: "date-time", }); expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(` { "$schema": "https://json-schema.org/draft/2020-12/schema", "format": "date-time", "type": "string", } `); }); test("cycle detection - root", () => { const schema = z.object({ name: z.string(), get subcategories() { return z.array(schema); }, }); expect(() => z.toJSONSchema(schema, { cycles: "throw" })).toThrowErrorMatchingInlineSnapshot(` [Error: Cycle detected: #/properties/subcategories/items/<root> Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.] `); }); test("cycle detection - mutual recursion", () => { const A = z.object({ name: z.string(), get subcategories() { return z.array(B); }, }); const B = z.object({ name: z.string(), get subcategories() { return z.array(A); }, }); expect(() => z.toJSONSchema(A, { cycles: "throw" })).toThrowErrorMatchingInlineSnapshot(` [Error: Cycle detected: #/properties/subcategories/items/properties/subcategories/items/<root> Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.] `); });
Save
cmd:
run