@gabrielbryk/json-schema-to-zod 2.12.1 → 2.14.0
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/.github/RELEASE_SETUP.md +120 -0
- package/.github/TOOLING_GUIDE.md +169 -0
- package/.github/dependabot.yml +52 -0
- package/.github/workflows/ci.yml +33 -0
- package/.github/workflows/release.yml +12 -4
- package/.github/workflows/security.yml +40 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.lintstagedrc.json +3 -0
- package/.prettierrc +20 -0
- package/AGENTS.md +7 -0
- package/CHANGELOG.md +13 -4
- package/README.md +24 -9
- package/commitlint.config.js +24 -0
- package/createIndex.ts +4 -4
- package/dist/cli.js +3 -4
- package/dist/core/analyzeSchema.js +56 -11
- package/dist/core/emitZod.js +43 -12
- package/dist/generators/generateBundle.js +67 -92
- package/dist/index.js +1 -0
- package/dist/parsers/parseAllOf.js +11 -12
- package/dist/parsers/parseAnyOf.js +2 -2
- package/dist/parsers/parseArray.js +38 -12
- package/dist/parsers/parseMultipleType.js +2 -2
- package/dist/parsers/parseNumber.js +44 -102
- package/dist/parsers/parseObject.js +136 -443
- package/dist/parsers/parseOneOf.js +57 -110
- package/dist/parsers/parseSchema.js +176 -71
- package/dist/parsers/parseSimpleDiscriminatedOneOf.js +2 -2
- package/dist/parsers/parseString.js +113 -253
- package/dist/types/Types.d.ts +37 -1
- package/dist/types/core/analyzeSchema.d.ts +4 -0
- package/dist/types/generators/generateBundle.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/utils/schemaNaming.d.ts +6 -0
- package/dist/utils/cliTools.js +1 -2
- package/dist/utils/esmEmitter.js +6 -2
- package/dist/utils/extractInlineObject.js +1 -3
- package/dist/utils/jsdocs.js +1 -4
- package/dist/utils/liftInlineObjects.js +76 -15
- package/dist/utils/resolveRef.js +35 -10
- package/dist/utils/schemaNaming.js +31 -0
- package/dist/utils/schemaRepresentation.js +35 -66
- package/dist/zodToJsonSchema.js +1 -2
- package/docs/IMPROVEMENT-PLAN.md +30 -12
- package/docs/ZOD-V4-RECURSIVE-TYPE-LIMITATIONS.md +70 -25
- package/docs/proposals/allof-required-merging.md +10 -4
- package/docs/proposals/bundle-refactor.md +10 -4
- package/docs/proposals/discriminated-union-with-default.md +18 -14
- package/docs/proposals/inline-object-lifting.md +15 -5
- package/docs/proposals/ref-anchor-support.md +11 -0
- package/output.txt +67 -0
- package/package.json +18 -5
- package/scripts/generateWorkflowSchema.ts +5 -14
- package/scripts/regenerate_bundle.ts +25 -0
- package/tsc_output.txt +542 -0
- package/tsc_output_2.txt +489 -0
|
@@ -2,12 +2,39 @@ import { withMessage } from "../utils/withMessage.js";
|
|
|
2
2
|
import { parseSchema } from "./parseSchema.js";
|
|
3
3
|
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
4
4
|
export const parseArray = (schema, refs) => {
|
|
5
|
-
|
|
5
|
+
// JSON Schema 2020-12 uses `prefixItems` for tuples.
|
|
6
|
+
// Older drafts used `items` as an array.
|
|
7
|
+
const prefixItems = schema.prefixItems || (Array.isArray(schema.items) ? schema.items : undefined);
|
|
8
|
+
if (prefixItems) {
|
|
6
9
|
// Tuple case
|
|
7
|
-
const itemResults =
|
|
8
|
-
let tuple = `z.tuple([${itemResults.map(r => r.expression).join(", ")}])`;
|
|
9
|
-
|
|
10
|
-
let
|
|
10
|
+
const itemResults = prefixItems.map((v, i) => parseSchema(v, { ...refs, path: [...refs.path, "prefixItems", i] }));
|
|
11
|
+
let tuple = `z.tuple([${itemResults.map((r) => r.expression).join(", ")}])`;
|
|
12
|
+
// We construct the type manually for the tuple part
|
|
13
|
+
let tupleTypes = itemResults.map((r) => r.type).join(", ");
|
|
14
|
+
let tupleType = `z.ZodTuple<[${tupleTypes}], null>`; // Default null rest
|
|
15
|
+
// Handle "additionalItems" (older drafts) or "items" (2020-12 when prefixItems is used)
|
|
16
|
+
// If prefixItems is present, `items` acts as the schema for additional items.
|
|
17
|
+
// If prefixItems came from `items` (array form), then `additionalItems` controls the rest.
|
|
18
|
+
const additionalSchema = schema.prefixItems ? schema.items : schema.additionalItems;
|
|
19
|
+
if (additionalSchema === false) {
|
|
20
|
+
// Closed tuple
|
|
21
|
+
}
|
|
22
|
+
else if (additionalSchema) {
|
|
23
|
+
const restSchema = additionalSchema === true
|
|
24
|
+
? anyOrUnknown(refs)
|
|
25
|
+
: parseSchema(additionalSchema, {
|
|
26
|
+
...refs,
|
|
27
|
+
path: [...refs.path, "items"],
|
|
28
|
+
});
|
|
29
|
+
tuple += `.rest(${restSchema.expression})`;
|
|
30
|
+
tupleType = `z.ZodTuple<[${tupleTypes}], ${restSchema.type}>`;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Open by default
|
|
34
|
+
const anyRes = anyOrUnknown(refs);
|
|
35
|
+
tuple += `.rest(${anyRes.expression})`;
|
|
36
|
+
tupleType = `z.ZodTuple<[${tupleTypes}], ${anyRes.type}>`;
|
|
37
|
+
}
|
|
11
38
|
if (schema.contains) {
|
|
12
39
|
const containsResult = parseSchema(schema.contains, {
|
|
13
40
|
...refs,
|
|
@@ -24,18 +51,18 @@ export const parseArray = (schema, refs) => {
|
|
|
24
51
|
ctx.addIssue({ code: "custom", message: "Array contains too many matching items" });
|
|
25
52
|
}
|
|
26
53
|
})`;
|
|
27
|
-
// In Zod v4, .superRefine() doesn't change the type
|
|
28
54
|
}
|
|
29
55
|
return {
|
|
30
56
|
expression: tuple,
|
|
31
57
|
type: tupleType,
|
|
32
58
|
};
|
|
33
59
|
}
|
|
34
|
-
// Array case
|
|
60
|
+
// Regular Array case
|
|
61
|
+
const itemsSchema = schema.items;
|
|
35
62
|
const anyOrUnknownResult = anyOrUnknown(refs);
|
|
36
|
-
const itemResult = !
|
|
63
|
+
const itemResult = !itemsSchema || itemsSchema === true
|
|
37
64
|
? anyOrUnknownResult
|
|
38
|
-
: parseSchema(
|
|
65
|
+
: parseSchema(itemsSchema, {
|
|
39
66
|
...refs,
|
|
40
67
|
path: [...refs.path, "items"],
|
|
41
68
|
});
|
|
@@ -44,13 +71,13 @@ export const parseArray = (schema, refs) => {
|
|
|
44
71
|
r += withMessage(schema, "minItems", ({ json }) => ({
|
|
45
72
|
opener: `.min(${json}`,
|
|
46
73
|
closer: ")",
|
|
47
|
-
messagePrefix: ", {
|
|
74
|
+
messagePrefix: ", { message: ",
|
|
48
75
|
messageCloser: " })",
|
|
49
76
|
}));
|
|
50
77
|
r += withMessage(schema, "maxItems", ({ json }) => ({
|
|
51
78
|
opener: `.max(${json}`,
|
|
52
79
|
closer: ")",
|
|
53
|
-
messagePrefix: ", {
|
|
80
|
+
messagePrefix: ", { message: ",
|
|
54
81
|
messageCloser: " })",
|
|
55
82
|
}));
|
|
56
83
|
if (schema.uniqueItems === true) {
|
|
@@ -94,7 +121,6 @@ export const parseArray = (schema, refs) => {
|
|
|
94
121
|
}
|
|
95
122
|
})`;
|
|
96
123
|
}
|
|
97
|
-
// In Zod v4, .superRefine() doesn't change the type, so no wrapping needed
|
|
98
124
|
return {
|
|
99
125
|
expression: r,
|
|
100
126
|
type: arrayType,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseSchema } from "./parseSchema.js";
|
|
2
2
|
export const parseMultipleType = (schema, refs) => {
|
|
3
3
|
const schemas = schema.type.map((type) => parseSchema({ ...schema, type }, { ...refs, withoutDefaults: true }));
|
|
4
|
-
const expressions = schemas.map(s => s.expression).join(", ");
|
|
5
|
-
const types = schemas.map(s => s.type).join(", ");
|
|
4
|
+
const expressions = schemas.map((s) => s.expression).join(", ");
|
|
5
|
+
const types = schemas.map((s) => s.type).join(", ");
|
|
6
6
|
return {
|
|
7
7
|
expression: `z.union([${expressions}])`,
|
|
8
8
|
type: `z.ZodUnion<[${types}]>`,
|
|
@@ -1,115 +1,57 @@
|
|
|
1
1
|
import { withMessage } from "../utils/withMessage.js";
|
|
2
2
|
export const parseNumber = (schema) => {
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
const formatMessage = schema.errorMessage?.format;
|
|
4
|
+
const formatParams = formatMessage ? `{ message: ${JSON.stringify(formatMessage)} }` : "";
|
|
5
|
+
const formatMap = {
|
|
6
|
+
int32: { expression: `z.int32(${formatParams})`, type: "z.ZodNumber" },
|
|
7
|
+
uint32: { expression: `z.uint32(${formatParams})`, type: "z.ZodNumber" },
|
|
8
|
+
float32: { expression: `z.float32(${formatParams})`, type: "z.ZodNumber" },
|
|
9
|
+
float64: { expression: `z.float64(${formatParams})`, type: "z.ZodNumber" },
|
|
10
|
+
safeint: { expression: `z.safeint(${formatParams})`, type: "z.ZodNumber" },
|
|
11
|
+
int64: { expression: `z.int64(${formatParams})`, type: "z.ZodBigInt" },
|
|
12
|
+
uint64: { expression: `z.uint64(${formatParams})`, type: "z.ZodBigInt" },
|
|
12
13
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
opener: ".int(",
|
|
20
|
-
closer: ")",
|
|
21
|
-
messagePrefix: "{ error: ",
|
|
22
|
-
messageCloser: " })",
|
|
23
|
-
}));
|
|
24
|
-
}
|
|
14
|
+
let r = schema.type === "integer" ? "z.int()" : "z.number()";
|
|
15
|
+
let zodType = schema.type === "integer" ? "z.ZodInt" : "z.ZodNumber";
|
|
16
|
+
if (schema.format && formatMap[schema.format]) {
|
|
17
|
+
const mapped = formatMap[schema.format];
|
|
18
|
+
r = mapped.expression;
|
|
19
|
+
zodType = mapped.type;
|
|
25
20
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
r += withMessage(schema, "multipleOf", ({ json }) => ({
|
|
22
|
+
opener: `.multipleOf(${json}`,
|
|
23
|
+
closer: ")",
|
|
24
|
+
messagePrefix: ", { message: ",
|
|
25
|
+
messageCloser: " })",
|
|
26
|
+
}));
|
|
27
|
+
const minimum = schema.minimum;
|
|
28
|
+
const maximum = schema.maximum;
|
|
29
|
+
const exclusiveMinimum = schema.exclusiveMinimum;
|
|
30
|
+
const exclusiveMaximum = schema.exclusiveMaximum;
|
|
31
|
+
const minMessage = schema.errorMessage?.minimum;
|
|
32
|
+
const maxMessage = schema.errorMessage?.maximum;
|
|
33
|
+
const exclMinMessage = schema.errorMessage?.exclusiveMinimum;
|
|
34
|
+
const exclMaxMessage = schema.errorMessage?.exclusiveMaximum;
|
|
35
|
+
if (typeof exclusiveMinimum === "number") {
|
|
36
|
+
r += `.gt(${exclusiveMinimum}${exclMinMessage ? `, { message: ${JSON.stringify(exclMinMessage)} }` : ""})`;
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (r.startsWith("z.number().int(")) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
opener: ".int(",
|
|
47
|
-
closer: ")",
|
|
48
|
-
messagePrefix: "{ error: ",
|
|
49
|
-
messageCloser: " })",
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
opener: `.multipleOf(${json}`,
|
|
54
|
-
closer: ")",
|
|
55
|
-
messagePrefix: ", { error: ",
|
|
56
|
-
messageCloser: " })",
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
if (typeof schema.minimum === "number") {
|
|
60
|
-
if (schema.exclusiveMinimum === true) {
|
|
61
|
-
r += withMessage(schema, "minimum", ({ json }) => ({
|
|
62
|
-
opener: `.gt(${json}`,
|
|
63
|
-
closer: ")",
|
|
64
|
-
messagePrefix: ", { error: ",
|
|
65
|
-
messageCloser: " })",
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
r += withMessage(schema, "minimum", ({ json }) => ({
|
|
70
|
-
opener: `.gte(${json}`,
|
|
71
|
-
closer: ")",
|
|
72
|
-
messagePrefix: ", { error: ",
|
|
73
|
-
messageCloser: " })",
|
|
74
|
-
}));
|
|
75
|
-
}
|
|
38
|
+
else if (exclusiveMinimum === true && typeof minimum === "number") {
|
|
39
|
+
r += `.gt(${minimum}${exclMinMessage ? `, { message: ${JSON.stringify(exclMinMessage)} }` : ""})`;
|
|
76
40
|
}
|
|
77
|
-
else if (typeof
|
|
78
|
-
r +=
|
|
79
|
-
opener: `.gt(${json}`,
|
|
80
|
-
closer: ")",
|
|
81
|
-
messagePrefix: ", { error: ",
|
|
82
|
-
messageCloser: " })",
|
|
83
|
-
}));
|
|
41
|
+
else if (typeof minimum === "number") {
|
|
42
|
+
r += `.min(${minimum}${minMessage ? `, { message: ${JSON.stringify(minMessage)} }` : ""})`;
|
|
84
43
|
}
|
|
85
|
-
if (typeof
|
|
86
|
-
|
|
87
|
-
r += withMessage(schema, "maximum", ({ json }) => ({
|
|
88
|
-
opener: `.lt(${json}`,
|
|
89
|
-
closer: ")",
|
|
90
|
-
messagePrefix: ", { error: ",
|
|
91
|
-
messageCloser: " })",
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
r += withMessage(schema, "maximum", ({ json }) => ({
|
|
96
|
-
opener: `.lte(${json}`,
|
|
97
|
-
closer: ")",
|
|
98
|
-
messagePrefix: ", { error: ",
|
|
99
|
-
messageCloser: " })",
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
44
|
+
if (typeof exclusiveMaximum === "number") {
|
|
45
|
+
r += `.lt(${exclusiveMaximum}${exclMaxMessage ? `, { message: ${JSON.stringify(exclMaxMessage)} }` : ""})`;
|
|
102
46
|
}
|
|
103
|
-
else if (typeof
|
|
104
|
-
r +=
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
messageCloser: " })",
|
|
109
|
-
}));
|
|
47
|
+
else if (exclusiveMaximum === true && typeof maximum === "number") {
|
|
48
|
+
r += `.lt(${maximum}${exclMaxMessage ? `, { message: ${JSON.stringify(exclMaxMessage)} }` : ""})`;
|
|
49
|
+
}
|
|
50
|
+
else if (typeof maximum === "number") {
|
|
51
|
+
r += `.max(${maximum}${maxMessage ? `, { message: ${JSON.stringify(maxMessage)} }` : ""})`;
|
|
110
52
|
}
|
|
111
53
|
return {
|
|
112
54
|
expression: r,
|
|
113
|
-
type:
|
|
55
|
+
type: zodType,
|
|
114
56
|
};
|
|
115
57
|
};
|