@gabrielbryk/json-schema-to-zod 2.9.0 → 2.10.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/CHANGELOG.md CHANGED
@@ -11,6 +11,11 @@
11
11
  ### Patch Changes
12
12
 
13
13
  - b8f6248: - Commit remaining utility, ref resolution test, and config updates.
14
+ - 04b6c6b: Fix bundle output by emitting z.lazy for cyclical refs outside object properties, use ZodError.issues in generated conditionals, and make nested type extraction array-aware to avoid invalid indexers.
15
+ - b8f7b29: Fix type errors in CI by replacing symbol-based allOf indexing, guarding invalid refs, and tightening string content schema parsing types.
16
+ - 691cc5b: - Added ESLint (recommended + no-require-imports) and cleaned all lint issues across src/tests; tightened types and removed unused vars.
17
+ - Ensured ESM-friendly test evals and ref/anchor resolver code without require usage.
18
+ - 4d127fe: Remove fallback to the non-existent ZodError.errors property in generated conditional schemas; rely on ZodError.issues to avoid TypeScript errors.
14
19
  - 7d257dd: - Ensure dist/esm emits real ESM with NodeNext settings and type:module, and update tests to run under ESM by providing createRequire shims.
15
20
 
16
21
  ## 2.8.0
@@ -101,8 +101,8 @@ const emitZod = (analysis) => {
101
101
  })
102
102
  .join("\n")
103
103
  : "";
104
- const jsdocs = rest.withJsdocs && typeof schema !== "boolean" && schema.description
105
- ? (0, jsdocs_js_1.expandJsdocs)(schema.description)
104
+ const jsdocs = rest.withJsdocs && typeof schema === "object" && schema !== null && "description" in schema
105
+ ? (0, jsdocs_js_1.expandJsdocs)(String(schema.description ?? ""))
106
106
  : "";
107
107
  const lines = [];
108
108
  if (module === "cjs" && !noImport) {
@@ -83,6 +83,7 @@ const buildBundleContext = (defNames, defs, options) => {
83
83
  return { defInfoMap, rootName, rootTypeName };
84
84
  };
85
85
  const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
86
+ const useLazyCrossRefs = options.refResolution?.lazyCrossRefs ?? true;
86
87
  return (schema, refs) => {
87
88
  if (typeof schema["$ref"] === "string") {
88
89
  const refPath = schema["$ref"];
@@ -109,7 +110,7 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
109
110
  });
110
111
  if (resolved)
111
112
  return resolved;
112
- if (isCycle && options.refResolution?.lazyCrossRefs) {
113
+ if (isCycle && useLazyCrossRefs) {
113
114
  return `z.lazy(() => ${refInfo.schemaName})`;
114
115
  }
115
116
  return refInfo.schemaName;
@@ -289,7 +290,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
289
290
  }
290
291
  // inline $defs
291
292
  if (record.$defs && typeof record.$defs === "object") {
292
- for (const [_defName, defSchema] of Object.entries(record.$defs)) {
293
+ for (const [, defSchema] of Object.entries(record.$defs)) {
293
294
  nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
294
295
  }
295
296
  }
@@ -338,14 +339,23 @@ const generateNestedTypesFile = (nestedTypes) => {
338
339
  lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
339
340
  }
340
341
  lines.push("");
342
+ const buildAccessExpr = (parentType, propertyPath) => {
343
+ let accessExpr = parentType;
344
+ for (const prop of propertyPath) {
345
+ const accessor = prop === "items"
346
+ ? "[number]"
347
+ : typeof prop === "number"
348
+ ? `[${prop}]`
349
+ : `[${JSON.stringify(prop)}]`;
350
+ accessExpr = `NonNullable<${accessExpr}${accessor}>`;
351
+ }
352
+ return accessExpr;
353
+ };
341
354
  for (const [parentType, types] of [...byParent.entries()].sort()) {
342
355
  lines.push(`// From ${parentType}`);
343
356
  for (const info of types.sort((a, b) => a.typeName.localeCompare(b.typeName))) {
344
357
  if (info.propertyPath.length > 0) {
345
- let accessExpr = parentType;
346
- for (const prop of info.propertyPath) {
347
- accessExpr = `NonNullable<${accessExpr}['${prop}']>`;
348
- }
358
+ const accessExpr = buildAccessExpr(parentType, info.propertyPath);
349
359
  lines.push(`export type ${info.typeName} = ${accessExpr};`);
350
360
  }
351
361
  }
@@ -3,19 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseAllOf = parseAllOf;
4
4
  const parseSchema_js_1 = require("./parseSchema.js");
5
5
  const half_js_1 = require("../utils/half.js");
6
- const originalIndex = Symbol("Original index");
6
+ const originalIndexKey = "__originalIndex";
7
7
  const ensureOriginalIndex = (arr) => {
8
- let newArr = [];
8
+ const newArr = [];
9
9
  for (let i = 0; i < arr.length; i++) {
10
10
  const item = arr[i];
11
11
  if (typeof item === "boolean") {
12
- newArr.push(item ? { [originalIndex]: i } : { [originalIndex]: i, not: {} });
12
+ newArr.push(item ? { [originalIndexKey]: i } : { [originalIndexKey]: i, not: {} });
13
13
  }
14
- else if (originalIndex in item) {
14
+ else if (typeof item === "object" &&
15
+ item !== null &&
16
+ originalIndexKey in item) {
15
17
  return arr;
16
18
  }
17
19
  else {
18
- newArr.push({ ...item, [originalIndex]: i });
20
+ newArr.push({ ...item, [originalIndexKey]: i });
19
21
  }
20
22
  }
21
23
  return newArr;
@@ -28,7 +30,11 @@ function parseAllOf(schema, refs) {
28
30
  const item = schema.allOf[0];
29
31
  return (0, parseSchema_js_1.parseSchema)(item, {
30
32
  ...refs,
31
- path: [...refs.path, "allOf", item[originalIndex]],
33
+ path: [
34
+ ...refs.path,
35
+ "allOf",
36
+ item[originalIndexKey] ?? 0,
37
+ ],
32
38
  });
33
39
  }
34
40
  else {
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseBoolean = void 0;
4
- const parseBoolean = (_schema) => {
5
- return "z.boolean()";
6
- };
4
+ const parseBoolean = () => "z.boolean()";
7
5
  exports.parseBoolean = parseBoolean;
@@ -17,7 +17,7 @@ const parseIfThenElse = (schema, refs) => {
17
17
  ? ${$then}.safeParse(value)
18
18
  : ${$else}.safeParse(value);
19
19
  if (!result.success) {
20
- const issues = result.error.issues ?? result.error.errors ?? [];
20
+ const issues = result.error.issues;
21
21
  issues.forEach((issue) => ctx.addIssue(issue))
22
22
  }
23
23
  })`;
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseNull = void 0;
4
- const parseNull = (_schema) => {
5
- return "z.null()";
6
- };
4
+ const parseNull = () => "z.null()";
7
5
  exports.parseNull = parseNull;
@@ -200,6 +200,7 @@ function parseObject(objectSchema, refs) {
200
200
  output += `.and(${(0, parseAnyOf_js_1.parseAnyOf)({
201
201
  ...objectSchema,
202
202
  anyOf: objectSchema.anyOf.map((x) => typeof x === "object" &&
203
+ x !== null &&
203
204
  !x.type &&
204
205
  (x.properties || x.additionalProperties || x.patternProperties)
205
206
  ? { ...x, type: "object" }
@@ -210,6 +211,7 @@ function parseObject(objectSchema, refs) {
210
211
  output += `.and(${(0, parseOneOf_js_1.parseOneOf)({
211
212
  ...objectSchema,
212
213
  oneOf: objectSchema.oneOf.map((x) => typeof x === "object" &&
214
+ x !== null &&
213
215
  !x.type &&
214
216
  (x.properties || x.additionalProperties || x.patternProperties)
215
217
  ? { ...x, type: "object" }
@@ -220,6 +222,7 @@ function parseObject(objectSchema, refs) {
220
222
  output += `.and(${(0, parseAllOf_js_1.parseAllOf)({
221
223
  ...objectSchema,
222
224
  allOf: objectSchema.allOf.map((x) => typeof x === "object" &&
225
+ x !== null &&
223
226
  !x.type &&
224
227
  (x.properties || x.additionalProperties || x.patternProperties)
225
228
  ? { ...x, type: "object" }
@@ -233,6 +236,7 @@ function parseObject(objectSchema, refs) {
233
236
  // propertyNames
234
237
  if (objectSchema.propertyNames) {
235
238
  const normalizedPropNames = typeof objectSchema.propertyNames === "object" &&
239
+ objectSchema.propertyNames !== null &&
236
240
  !objectSchema.propertyNames.type &&
237
241
  objectSchema.propertyNames.pattern
238
242
  ? { ...objectSchema.propertyNames, type: "string" }
@@ -261,12 +265,12 @@ function parseObject(objectSchema, refs) {
261
265
  if (entries.length) {
262
266
  output += `.superRefine((obj, ctx) => {
263
267
  ${entries
264
- .map(([key, schema], idx) => {
268
+ .map(([key, schema]) => {
265
269
  const parsed = (0, parseSchema_js_1.parseSchema)(schema, { ...refs, path: [...refs.path, "dependentSchemas", key] });
266
270
  return `if (Object.prototype.hasOwnProperty.call(obj, ${JSON.stringify(key)})) {
267
271
  const result = ${parsed}.safeParse(obj);
268
272
  if (!result.success) {
269
- ctx.addIssue({ code: "custom", message: "Dependent schema failed", path: [], params: { issues: result.error.issues } });
273
+ ctx.addIssue({ code: "custom", message: ${objectSchema.errorMessage?.dependentSchemas ?? JSON.stringify("Dependent schema failed")}, path: [], params: { issues: result.error.issues } });
270
274
  }
271
275
  }`;
272
276
  })
@@ -278,7 +282,8 @@ function parseObject(objectSchema, refs) {
278
282
  if (objectSchema.dependentRequired && typeof objectSchema.dependentRequired === "object") {
279
283
  const entries = Object.entries(objectSchema.dependentRequired);
280
284
  if (entries.length) {
281
- const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ?? "Dependent required properties missing";
285
+ const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ??
286
+ "Dependent required properties missing";
282
287
  output += `.superRefine((obj, ctx) => {
283
288
  ${entries
284
289
  .map(([prop, deps]) => {
@@ -33,9 +33,7 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
33
33
  if (typeof schema !== "object")
34
34
  return schema ? (0, anyOrUnknown_js_1.anyOrUnknown)(refs) : "z.never()";
35
35
  const parentBase = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
36
- const baseUri = typeof schema.$id === "string"
37
- ? (0, resolveUri_js_1.resolveUri)(parentBase, schema.$id)
38
- : parentBase;
36
+ const baseUri = typeof schema.$id === "string" ? (0, resolveUri_js_1.resolveUri)(parentBase, schema.$id) : parentBase;
39
37
  const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
40
38
  if (typeof schema.$dynamicAnchor === "string") {
41
39
  dynamicAnchors.push({
@@ -85,6 +83,9 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
85
83
  exports.parseSchema = parseSchema;
86
84
  const parseRef = (schema, refs) => {
87
85
  const refValue = schema.$dynamicRef ?? schema.$ref;
86
+ if (typeof refValue !== "string") {
87
+ return (0, anyOrUnknown_js_1.anyOrUnknown)(refs);
88
+ }
88
89
  const resolved = resolveRef(schema, refValue, refs);
89
90
  if (!resolved) {
90
91
  refs.onUnresolvedRef?.(refValue, refs.path);
@@ -194,7 +195,10 @@ const resolveRef = (schemaNode, ref, refs) => {
194
195
  const loaded = refs.resolveExternalRef(extBase);
195
196
  if (loaded) {
196
197
  // If async resolver is used synchronously here, it will be ignored; keep simple sync for now
197
- const schema = loaded.then ? undefined : loaded;
198
+ const maybePromise = loaded;
199
+ const schema = typeof maybePromise.then === "function"
200
+ ? undefined
201
+ : loaded;
198
202
  if (schema) {
199
203
  const { registry } = (0, buildRefRegistry_js_1.buildRefRegistry)(schema, extBase);
200
204
  registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
@@ -342,10 +346,10 @@ const selectParser = (schema, refs) => {
342
346
  return (0, parseNumber_js_1.parseNumber)(schema);
343
347
  }
344
348
  else if (exports.its.a.primitive(schema, "boolean")) {
345
- return (0, parseBoolean_js_1.parseBoolean)(schema);
349
+ return (0, parseBoolean_js_1.parseBoolean)();
346
350
  }
347
351
  else if (exports.its.a.primitive(schema, "null")) {
348
- return (0, parseNull_js_1.parseNull)(schema);
352
+ return (0, parseNull_js_1.parseNull)();
349
353
  }
350
354
  else if (exports.its.a.conditional(schema)) {
351
355
  return (0, parseIfThenElse_js_1.parseIfThenElse)(schema, refs);
@@ -289,7 +289,7 @@ const parseString = (schema, refs) => {
289
289
  if (contentMediaType != "") {
290
290
  r += contentMediaType;
291
291
  r += (0, withMessage_js_1.withMessage)(schema, "contentSchema", ({ value }) => {
292
- if (value && value instanceof Object) {
292
+ if (value && typeof value === "object") {
293
293
  return {
294
294
  opener: `.pipe(${(0, parseSchema_js_1.parseSchema)(value, refContext)}`,
295
295
  closer: ")",
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildRefRegistry = void 0;
4
4
  const resolveUri_js_1 = require("./resolveUri.js");
5
- const buildRefRegistry = (schema, rootBaseUri = "root:///", opts = {}) => {
5
+ const buildRefRegistry = (schema, rootBaseUri = "root:///") => {
6
6
  const registry = new Map();
7
7
  const walk = (node, baseUri, path) => {
8
8
  if (typeof node !== "object" || node === null)
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.omit = void 0;
4
4
  const omit = (obj, ...keys) => Object.keys(obj).reduce((acc, key) => {
5
- if (!keys.includes(key)) {
6
- acc[key] = obj[key];
5
+ const typedKey = key;
6
+ if (!keys.includes(typedKey)) {
7
+ acc[typedKey] = obj[typedKey];
7
8
  }
8
9
  return acc;
9
10
  }, {});
@@ -98,8 +98,8 @@ export const emitZod = (analysis) => {
98
98
  })
99
99
  .join("\n")
100
100
  : "";
101
- const jsdocs = rest.withJsdocs && typeof schema !== "boolean" && schema.description
102
- ? expandJsdocs(schema.description)
101
+ const jsdocs = rest.withJsdocs && typeof schema === "object" && schema !== null && "description" in schema
102
+ ? expandJsdocs(String(schema.description ?? ""))
103
103
  : "";
104
104
  const lines = [];
105
105
  if (module === "cjs" && !noImport) {
@@ -79,6 +79,7 @@ const buildBundleContext = (defNames, defs, options) => {
79
79
  return { defInfoMap, rootName, rootTypeName };
80
80
  };
81
81
  const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
82
+ const useLazyCrossRefs = options.refResolution?.lazyCrossRefs ?? true;
82
83
  return (schema, refs) => {
83
84
  if (typeof schema["$ref"] === "string") {
84
85
  const refPath = schema["$ref"];
@@ -105,7 +106,7 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
105
106
  });
106
107
  if (resolved)
107
108
  return resolved;
108
- if (isCycle && options.refResolution?.lazyCrossRefs) {
109
+ if (isCycle && useLazyCrossRefs) {
109
110
  return `z.lazy(() => ${refInfo.schemaName})`;
110
111
  }
111
112
  return refInfo.schemaName;
@@ -285,7 +286,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
285
286
  }
286
287
  // inline $defs
287
288
  if (record.$defs && typeof record.$defs === "object") {
288
- for (const [_defName, defSchema] of Object.entries(record.$defs)) {
289
+ for (const [, defSchema] of Object.entries(record.$defs)) {
289
290
  nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
290
291
  }
291
292
  }
@@ -334,14 +335,23 @@ const generateNestedTypesFile = (nestedTypes) => {
334
335
  lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
335
336
  }
336
337
  lines.push("");
338
+ const buildAccessExpr = (parentType, propertyPath) => {
339
+ let accessExpr = parentType;
340
+ for (const prop of propertyPath) {
341
+ const accessor = prop === "items"
342
+ ? "[number]"
343
+ : typeof prop === "number"
344
+ ? `[${prop}]`
345
+ : `[${JSON.stringify(prop)}]`;
346
+ accessExpr = `NonNullable<${accessExpr}${accessor}>`;
347
+ }
348
+ return accessExpr;
349
+ };
337
350
  for (const [parentType, types] of [...byParent.entries()].sort()) {
338
351
  lines.push(`// From ${parentType}`);
339
352
  for (const info of types.sort((a, b) => a.typeName.localeCompare(b.typeName))) {
340
353
  if (info.propertyPath.length > 0) {
341
- let accessExpr = parentType;
342
- for (const prop of info.propertyPath) {
343
- accessExpr = `NonNullable<${accessExpr}['${prop}']>`;
344
- }
354
+ const accessExpr = buildAccessExpr(parentType, info.propertyPath);
345
355
  lines.push(`export type ${info.typeName} = ${accessExpr};`);
346
356
  }
347
357
  }
@@ -1,18 +1,20 @@
1
1
  import { parseSchema } from "./parseSchema.js";
2
2
  import { half } from "../utils/half.js";
3
- const originalIndex = Symbol("Original index");
3
+ const originalIndexKey = "__originalIndex";
4
4
  const ensureOriginalIndex = (arr) => {
5
- let newArr = [];
5
+ const newArr = [];
6
6
  for (let i = 0; i < arr.length; i++) {
7
7
  const item = arr[i];
8
8
  if (typeof item === "boolean") {
9
- newArr.push(item ? { [originalIndex]: i } : { [originalIndex]: i, not: {} });
9
+ newArr.push(item ? { [originalIndexKey]: i } : { [originalIndexKey]: i, not: {} });
10
10
  }
11
- else if (originalIndex in item) {
11
+ else if (typeof item === "object" &&
12
+ item !== null &&
13
+ originalIndexKey in item) {
12
14
  return arr;
13
15
  }
14
16
  else {
15
- newArr.push({ ...item, [originalIndex]: i });
17
+ newArr.push({ ...item, [originalIndexKey]: i });
16
18
  }
17
19
  }
18
20
  return newArr;
@@ -25,7 +27,11 @@ export function parseAllOf(schema, refs) {
25
27
  const item = schema.allOf[0];
26
28
  return parseSchema(item, {
27
29
  ...refs,
28
- path: [...refs.path, "allOf", item[originalIndex]],
30
+ path: [
31
+ ...refs.path,
32
+ "allOf",
33
+ item[originalIndexKey] ?? 0,
34
+ ],
29
35
  });
30
36
  }
31
37
  else {
@@ -1,3 +1 @@
1
- export const parseBoolean = (_schema) => {
2
- return "z.boolean()";
3
- };
1
+ export const parseBoolean = () => "z.boolean()";
@@ -14,7 +14,7 @@ export const parseIfThenElse = (schema, refs) => {
14
14
  ? ${$then}.safeParse(value)
15
15
  : ${$else}.safeParse(value);
16
16
  if (!result.success) {
17
- const issues = result.error.issues ?? result.error.errors ?? [];
17
+ const issues = result.error.issues;
18
18
  issues.forEach((issue) => ctx.addIssue(issue))
19
19
  }
20
20
  })`;
@@ -1,3 +1 @@
1
- export const parseNull = (_schema) => {
2
- return "z.null()";
3
- };
1
+ export const parseNull = () => "z.null()";
@@ -197,6 +197,7 @@ export function parseObject(objectSchema, refs) {
197
197
  output += `.and(${parseAnyOf({
198
198
  ...objectSchema,
199
199
  anyOf: objectSchema.anyOf.map((x) => typeof x === "object" &&
200
+ x !== null &&
200
201
  !x.type &&
201
202
  (x.properties || x.additionalProperties || x.patternProperties)
202
203
  ? { ...x, type: "object" }
@@ -207,6 +208,7 @@ export function parseObject(objectSchema, refs) {
207
208
  output += `.and(${parseOneOf({
208
209
  ...objectSchema,
209
210
  oneOf: objectSchema.oneOf.map((x) => typeof x === "object" &&
211
+ x !== null &&
210
212
  !x.type &&
211
213
  (x.properties || x.additionalProperties || x.patternProperties)
212
214
  ? { ...x, type: "object" }
@@ -217,6 +219,7 @@ export function parseObject(objectSchema, refs) {
217
219
  output += `.and(${parseAllOf({
218
220
  ...objectSchema,
219
221
  allOf: objectSchema.allOf.map((x) => typeof x === "object" &&
222
+ x !== null &&
220
223
  !x.type &&
221
224
  (x.properties || x.additionalProperties || x.patternProperties)
222
225
  ? { ...x, type: "object" }
@@ -230,6 +233,7 @@ export function parseObject(objectSchema, refs) {
230
233
  // propertyNames
231
234
  if (objectSchema.propertyNames) {
232
235
  const normalizedPropNames = typeof objectSchema.propertyNames === "object" &&
236
+ objectSchema.propertyNames !== null &&
233
237
  !objectSchema.propertyNames.type &&
234
238
  objectSchema.propertyNames.pattern
235
239
  ? { ...objectSchema.propertyNames, type: "string" }
@@ -258,12 +262,12 @@ export function parseObject(objectSchema, refs) {
258
262
  if (entries.length) {
259
263
  output += `.superRefine((obj, ctx) => {
260
264
  ${entries
261
- .map(([key, schema], idx) => {
265
+ .map(([key, schema]) => {
262
266
  const parsed = parseSchema(schema, { ...refs, path: [...refs.path, "dependentSchemas", key] });
263
267
  return `if (Object.prototype.hasOwnProperty.call(obj, ${JSON.stringify(key)})) {
264
268
  const result = ${parsed}.safeParse(obj);
265
269
  if (!result.success) {
266
- ctx.addIssue({ code: "custom", message: "Dependent schema failed", path: [], params: { issues: result.error.issues } });
270
+ ctx.addIssue({ code: "custom", message: ${objectSchema.errorMessage?.dependentSchemas ?? JSON.stringify("Dependent schema failed")}, path: [], params: { issues: result.error.issues } });
267
271
  }
268
272
  }`;
269
273
  })
@@ -275,7 +279,8 @@ export function parseObject(objectSchema, refs) {
275
279
  if (objectSchema.dependentRequired && typeof objectSchema.dependentRequired === "object") {
276
280
  const entries = Object.entries(objectSchema.dependentRequired);
277
281
  if (entries.length) {
278
- const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ?? "Dependent required properties missing";
282
+ const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ??
283
+ "Dependent required properties missing";
279
284
  output += `.superRefine((obj, ctx) => {
280
285
  ${entries
281
286
  .map(([prop, deps]) => {
@@ -30,9 +30,7 @@ export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockM
30
30
  if (typeof schema !== "object")
31
31
  return schema ? anyOrUnknown(refs) : "z.never()";
32
32
  const parentBase = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
33
- const baseUri = typeof schema.$id === "string"
34
- ? resolveUri(parentBase, schema.$id)
35
- : parentBase;
33
+ const baseUri = typeof schema.$id === "string" ? resolveUri(parentBase, schema.$id) : parentBase;
36
34
  const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
37
35
  if (typeof schema.$dynamicAnchor === "string") {
38
36
  dynamicAnchors.push({
@@ -81,6 +79,9 @@ export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockM
81
79
  };
82
80
  const parseRef = (schema, refs) => {
83
81
  const refValue = schema.$dynamicRef ?? schema.$ref;
82
+ if (typeof refValue !== "string") {
83
+ return anyOrUnknown(refs);
84
+ }
84
85
  const resolved = resolveRef(schema, refValue, refs);
85
86
  if (!resolved) {
86
87
  refs.onUnresolvedRef?.(refValue, refs.path);
@@ -190,7 +191,10 @@ const resolveRef = (schemaNode, ref, refs) => {
190
191
  const loaded = refs.resolveExternalRef(extBase);
191
192
  if (loaded) {
192
193
  // If async resolver is used synchronously here, it will be ignored; keep simple sync for now
193
- const schema = loaded.then ? undefined : loaded;
194
+ const maybePromise = loaded;
195
+ const schema = typeof maybePromise.then === "function"
196
+ ? undefined
197
+ : loaded;
194
198
  if (schema) {
195
199
  const { registry } = buildRefRegistry(schema, extBase);
196
200
  registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
@@ -338,10 +342,10 @@ const selectParser = (schema, refs) => {
338
342
  return parseNumber(schema);
339
343
  }
340
344
  else if (its.a.primitive(schema, "boolean")) {
341
- return parseBoolean(schema);
345
+ return parseBoolean();
342
346
  }
343
347
  else if (its.a.primitive(schema, "null")) {
344
- return parseNull(schema);
348
+ return parseNull();
345
349
  }
346
350
  else if (its.a.conditional(schema)) {
347
351
  return parseIfThenElse(schema, refs);
@@ -286,7 +286,7 @@ export const parseString = (schema, refs) => {
286
286
  if (contentMediaType != "") {
287
287
  r += contentMediaType;
288
288
  r += withMessage(schema, "contentSchema", ({ value }) => {
289
- if (value && value instanceof Object) {
289
+ if (value && typeof value === "object") {
290
290
  return {
291
291
  opener: `.pipe(${parseSchema(value, refContext)}`,
292
292
  closer: ")",
@@ -1,5 +1,5 @@
1
1
  import { resolveUri } from "./resolveUri.js";
2
- export const buildRefRegistry = (schema, rootBaseUri = "root:///", opts = {}) => {
2
+ export const buildRefRegistry = (schema, rootBaseUri = "root:///") => {
3
3
  const registry = new Map();
4
4
  const walk = (node, baseUri, path) => {
5
5
  if (typeof node !== "object" || node === null)
@@ -1,6 +1,7 @@
1
1
  export const omit = (obj, ...keys) => Object.keys(obj).reduce((acc, key) => {
2
- if (!keys.includes(key)) {
3
- acc[key] = obj[key];
2
+ const typedKey = key;
3
+ if (!keys.includes(typedKey)) {
4
+ acc[typedKey] = obj[typedKey];
4
5
  }
5
6
  return acc;
6
7
  }, {});
@@ -6,6 +6,11 @@ export type JsonSchemaObject = {
6
6
  type?: string | string[];
7
7
  $id?: string;
8
8
  $ref?: string;
9
+ $anchor?: string;
10
+ $dynamicRef?: string;
11
+ $dynamicAnchor?: string;
12
+ $recursiveRef?: string;
13
+ $recursiveAnchor?: boolean;
9
14
  $defs?: Record<string, JsonSchema>;
10
15
  definitions?: Record<string, JsonSchema>;
11
16
  title?: string;
@@ -53,9 +58,7 @@ export type JsonSchemaObject = {
53
58
  errorMessage?: {
54
59
  [key: string]: string | undefined;
55
60
  };
56
- } & {
57
- [key: string]: any;
58
- };
61
+ } & Record<string, unknown>;
59
62
  export type ParserSelector = (schema: JsonSchemaObject, refs: Refs) => string;
60
63
  export type ParserOverride = (schema: JsonSchemaObject, refs: Refs) => string | void;
61
64
  export type Options = {
@@ -1,4 +1 @@
1
- import { JsonSchemaObject } from "../Types.js";
2
- export declare const parseBoolean: (_schema: JsonSchemaObject & {
3
- type: "boolean";
4
- }) => string;
1
+ export declare const parseBoolean: () => string;
@@ -1,4 +1 @@
1
- import { JsonSchemaObject } from "../Types.js";
2
- export declare const parseNull: (_schema: JsonSchemaObject & {
3
- type: "null";
4
- }) => string;
1
+ export declare const parseNull: () => string;
@@ -1,4 +1,4 @@
1
- import { JsonSchema, Options } from "../Types.js";
1
+ import { JsonSchema } from "../Types.js";
2
2
  export type RefRegistryEntry = {
3
3
  schema: JsonSchema;
4
4
  path: (string | number)[];
@@ -6,7 +6,7 @@ export type RefRegistryEntry = {
6
6
  dynamic?: boolean;
7
7
  anchor?: string;
8
8
  };
9
- export declare const buildRefRegistry: (schema: JsonSchema, rootBaseUri?: string, opts?: Options) => {
9
+ export declare const buildRefRegistry: (schema: JsonSchema, rootBaseUri?: string) => {
10
10
  registry: Map<string, RefRegistryEntry>;
11
11
  rootBaseUri: string;
12
12
  };
package/eslint.config.js CHANGED
@@ -3,7 +3,7 @@ import pluginTs from "@typescript-eslint/eslint-plugin";
3
3
 
4
4
  export default [
5
5
  {
6
- ignores: ["dist", "node_modules"],
6
+ ignores: ["dist", "node_modules", "test/output/**"],
7
7
  },
8
8
  {
9
9
  files: ["**/*.ts", "**/*.tsx"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gabrielbryk/json-schema-to-zod",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Converts JSON schema objects or files into Zod schemas",
5
5
  "type": "module",
6
6
  "types": "./dist/types/index.d.ts",