@signe/schema-to-zod 2.8.3 → 2.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signe/schema-to-zod",
3
- "version": "2.8.3",
3
+ "version": "2.9.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -92,6 +92,52 @@ Parameters:
92
92
  Returns:
93
93
  - A record of Zod type definitions that can be used with `z.object()`
94
94
 
95
+ ### `jsonSchemaToZodSchema(schema: JSONSchema7 | JSONSchema7Definition[]): ZodType<unknown>`
96
+
97
+ Converts a JSON Schema to a full Zod schema.
98
+
99
+ Use this API when the schema relies on:
100
+ - `anyOf`
101
+ - `not`
102
+ - `allOf`
103
+ - `dependentRequired`
104
+ - `dependentSchemas`
105
+ - `if / then / else`
106
+ - conditional branches that must validate only the active fields
107
+
108
+ Example:
109
+
110
+ ```typescript
111
+ import { jsonSchemaToZodSchema } from '@signe/schema-to-zod';
112
+
113
+ const schema = {
114
+ type: 'object',
115
+ properties: {
116
+ name: { type: 'string' },
117
+ itemType: { type: 'string', enum: ['item', 'weapon', 'armor'] }
118
+ },
119
+ required: ['name', 'itemType'],
120
+ allOf: [
121
+ {
122
+ if: {
123
+ properties: {
124
+ itemType: { const: 'weapon' }
125
+ }
126
+ },
127
+ then: {
128
+ properties: {
129
+ atk: { type: 'number' },
130
+ weaponType: { type: 'string' }
131
+ },
132
+ required: ['atk', 'weaponType']
133
+ }
134
+ }
135
+ ]
136
+ };
137
+
138
+ const zodSchema = jsonSchemaToZodSchema(schema);
139
+ ```
140
+
95
141
  ## Examples
96
142
 
97
143
  ### Nested Objects
package/src/index.ts CHANGED
@@ -1,18 +1,73 @@
1
- import { z, ZodType, ZodTypeDef } from "zod";
1
+ import { z, type RefinementCtx, type ZodRawShape, type ZodType } from "zod";
2
2
  import type { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from "json-schema";
3
3
 
4
4
  /**
5
5
  * Standard JSON Schema types only
6
6
  */
7
- type SchemaType = Exclude<JSONSchema7TypeName, 'array' | 'object'> | 'array' | 'object';
7
+ type SchemaType = Exclude<JSONSchema7TypeName, "array" | "object"> | "array" | "object";
8
+ type ExtendedJSONSchema7 = JSONSchema7 & {
9
+ dependentRequired?: Record<string, string[]>;
10
+ dependentSchemas?: Record<string, JSONSchema7Definition>;
11
+ };
8
12
 
9
13
  function isValidSchemaType(type: unknown): type is SchemaType {
10
- if (typeof type !== 'string') return false;
11
- return ['string', 'number', 'boolean', 'integer', 'array', 'object', 'null'].includes(type);
14
+ if (typeof type !== "string") return false;
15
+ return ["string", "number", "boolean", "integer", "array", "object", "null"].includes(type);
12
16
  }
13
17
 
14
18
  function isJSONSchema7(schema: JSONSchema7Definition): schema is JSONSchema7 {
15
- return typeof schema !== 'boolean';
19
+ return typeof schema !== "boolean";
20
+ }
21
+
22
+ function asExtendedJSONSchema7(schema: JSONSchema7): ExtendedJSONSchema7 {
23
+ return schema as ExtendedJSONSchema7;
24
+ }
25
+
26
+ function unwrapSchemaDefinition(schema: unknown): JSONSchema7Definition | null {
27
+ if (typeof schema === "boolean") {
28
+ return schema;
29
+ }
30
+
31
+ if (!schema || typeof schema !== "object") {
32
+ return null;
33
+ }
34
+
35
+ if ("schema" in schema) {
36
+ return unwrapSchemaDefinition((schema as { schema?: unknown }).schema);
37
+ }
38
+
39
+ return schema as JSONSchema7Definition;
40
+ }
41
+
42
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44
+ }
45
+
46
+ function areValuesEqual(left: unknown, right: unknown): boolean {
47
+ if (Object.is(left, right)) {
48
+ return true;
49
+ }
50
+
51
+ if (Array.isArray(left) && Array.isArray(right)) {
52
+ if (left.length !== right.length) {
53
+ return false;
54
+ }
55
+
56
+ return left.every((item, index) => areValuesEqual(item, right[index]));
57
+ }
58
+
59
+ if (isPlainObject(left) && isPlainObject(right)) {
60
+ const leftKeys = Object.keys(left);
61
+ const rightKeys = Object.keys(right);
62
+
63
+ if (leftKeys.length !== rightKeys.length) {
64
+ return false;
65
+ }
66
+
67
+ return leftKeys.every((key) => areValuesEqual(left[key], right[key]));
68
+ }
69
+
70
+ return false;
16
71
  }
17
72
 
18
73
  /**
@@ -24,66 +79,60 @@ const percent = z.number().min(0).max(100);
24
79
  * Map of JSON Schema formats to their corresponding Zod schema creators
25
80
  */
26
81
  const formatMap: Record<string, (schema: JSONSchema7) => ZodType<unknown>> = {
27
- 'date': () => z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
28
- 'date-time': () => z.string().datetime(),
29
- 'email': () => z.string().email(),
30
- 'hostname': () => z.string(),
31
- 'ipv4': () => z.string().ip({ version: 'v4' }),
32
- 'ipv6': () => z.string().ip({ version: 'v6' }),
33
- 'uri': () => z.string().url(),
34
- 'uuid': () => z.string().uuid(),
35
- 'color': () => z.string().regex(/^#(?:[0-9a-fA-F]{3}){1,2}$/),
36
- 'password': () => z.string(),
37
- 'code': () => z.string(),
38
- 'percent': () => percent,
82
+ "date": () => z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
83
+ "date-time": () => z.string().datetime(),
84
+ "email": () => z.string().email(),
85
+ "hostname": () => z.string(),
86
+ "ipv4": () => z.string().ip({ version: "v4" }),
87
+ "ipv6": () => z.string().ip({ version: "v6" }),
88
+ "uri": () => z.string().url(),
89
+ "uuid": () => z.string().uuid(),
90
+ "color": () => z.string().regex(/^#(?:[0-9a-fA-F]{3}){1,2}$/),
91
+ "password": () => z.string(),
92
+ "code": () => z.string(),
93
+ "percent": () => percent,
39
94
  };
40
95
 
41
- /**
42
- * Map of JSON Schema types to their corresponding Zod schema creators
43
- */
44
- const typeMap: Record<SchemaType, (schema: JSONSchema7) => ZodType<unknown, ZodTypeDef, unknown>> = {
45
- 'string': (schema) => {
46
- if (schema.format && formatMap[schema.format]) {
47
- return formatMap[schema.format](schema);
48
- }
49
- return z.string();
50
- },
51
- 'number': (schema) => {
52
- if (schema.format === 'percent') {
53
- return percent;
54
- }
55
- return z.number();
56
- },
57
- 'integer': () => z.number().int(),
58
- 'boolean': () => z.boolean(),
59
- 'null': () => z.null(),
60
- 'array': (schema: JSONSchema7) => {
61
- if (!schema.items || Array.isArray(schema.items)) {
62
- throw new Error('Invalid array items');
63
- }
96
+ function createRequiredUnknownSchema(): ZodType<unknown> {
97
+ return z.custom<unknown>((value) => value !== undefined);
98
+ }
64
99
 
65
- if (!isJSONSchema7(schema.items)) {
66
- throw new Error('Boolean schema is not supported for array items');
67
- }
100
+ function inferSchemaType(schema: JSONSchema7): SchemaType | null {
101
+ if (schema.type !== undefined && !isValidSchemaType(schema.type)) {
102
+ throw new Error(`Unsupported type: ${schema.type}`);
103
+ }
68
104
 
69
- if (schema.items.type === 'object') {
70
- return z.array(z.object(jsonSchemaToZod(schema.items)));
71
- }
105
+ if (schema.type && isValidSchemaType(schema.type)) {
106
+ return schema.type;
107
+ }
72
108
 
73
- const itemType = schema.items.type;
74
- if (!itemType || !isValidSchemaType(itemType)) {
75
- throw new Error(`Unsupported array item type: ${itemType}`);
76
- }
109
+ if (schema.properties || schema.required || schema.if || schema.then || schema.else || schema.allOf) {
110
+ return "object";
111
+ }
77
112
 
78
- return z.array(typeMap[itemType](schema.items));
79
- },
80
- 'object': (schema: JSONSchema7) => {
81
- if (!schema.properties) {
82
- throw new Error('Invalid object schema: missing properties');
83
- }
84
- return z.object(jsonSchemaToZod(schema));
113
+ if (schema.items) {
114
+ return "array";
85
115
  }
86
- };
116
+
117
+ if (
118
+ schema.pattern ||
119
+ schema.minLength !== undefined ||
120
+ schema.maxLength !== undefined ||
121
+ (schema.format && schema.format !== "percent")
122
+ ) {
123
+ return "string";
124
+ }
125
+
126
+ if (
127
+ schema.minimum !== undefined ||
128
+ schema.maximum !== undefined ||
129
+ schema.format === "percent"
130
+ ) {
131
+ return "number";
132
+ }
133
+
134
+ return null;
135
+ }
87
136
 
88
137
  /**
89
138
  * Applies validators to a Zod schema based on JSON Schema property constraints
@@ -96,7 +145,7 @@ function applyValidators(
96
145
  let zodTypeWithValidators = zodType;
97
146
 
98
147
  // String validators
99
- if (schema.type === 'string' && required) {
148
+ if (schema.type === "string" && required) {
100
149
  zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).min(1);
101
150
 
102
151
  if (schema.minLength !== undefined) {
@@ -109,7 +158,7 @@ function applyValidators(
109
158
  }
110
159
 
111
160
  // Array validators
112
- if (schema.type === 'array') {
161
+ if (schema.type === "array") {
113
162
  if (schema.minItems !== undefined) {
114
163
  zodTypeWithValidators = (zodTypeWithValidators as z.ZodArray<ZodType>).min(schema.minItems);
115
164
  }
@@ -119,7 +168,7 @@ function applyValidators(
119
168
  }
120
169
 
121
170
  // Number validators
122
- if (schema.type === 'number' || schema.type === 'integer') {
171
+ if (schema.type === "number" || schema.type === "integer") {
123
172
  if (schema.minimum !== undefined) {
124
173
  zodTypeWithValidators = (zodTypeWithValidators as z.ZodNumber).min(schema.minimum);
125
174
  }
@@ -129,29 +178,318 @@ function applyValidators(
129
178
  }
130
179
  }
131
180
 
181
+ // Pattern validators
182
+ if (schema.pattern) {
183
+ zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).regex(new RegExp(schema.pattern));
184
+ }
185
+
186
+ // Const validator
187
+ if (schema.const !== undefined) {
188
+ zodTypeWithValidators = zodTypeWithValidators.refine(
189
+ (value) => areValuesEqual(value, schema.const),
190
+ {
191
+ message: `Must be equal to: ${JSON.stringify(schema.const)}`,
192
+ }
193
+ );
194
+ }
195
+
132
196
  // Enum validators
133
197
  if (schema.enum) {
134
198
  zodTypeWithValidators = zodTypeWithValidators.refine(
135
- (value): value is typeof schema.enum[number] => schema.enum!.includes(value as string),
199
+ (value): value is typeof schema.enum[number] =>
200
+ schema.enum!.some((enumValue) => areValuesEqual(value, enumValue)),
136
201
  {
137
- message: `Must be one of: ${schema.enum.join(", ")}`,
202
+ message: `Must be one of: ${schema.enum.map((value) => JSON.stringify(value)).join(", ")}`,
138
203
  }
139
204
  );
140
205
  }
141
206
 
142
- // Pattern validators
143
- if (schema.pattern) {
144
- zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).regex(new RegExp(schema.pattern));
207
+ return zodTypeWithValidators;
208
+ }
209
+
210
+ function applyLogicalCompositionValidators(
211
+ zodType: ZodType<unknown>,
212
+ schema: JSONSchema7
213
+ ): ZodType<unknown> {
214
+ let composedType = zodType;
215
+
216
+ if (schema.anyOf && schema.anyOf.length > 0) {
217
+ composedType = composedType.superRefine((value, context) => {
218
+ const matchesAnySchema = schema.anyOf!.some((subSchema) => matchesSchemaDefinition(subSchema, value));
219
+
220
+ if (!matchesAnySchema) {
221
+ context.addIssue({
222
+ code: z.ZodIssueCode.custom,
223
+ message: "Must match at least one schema in anyOf",
224
+ });
225
+ }
226
+ });
145
227
  }
146
228
 
147
- return zodTypeWithValidators;
229
+ if (schema.not !== undefined) {
230
+ composedType = composedType.superRefine((value, context) => {
231
+ if (matchesSchemaDefinition(schema.not!, value)) {
232
+ context.addIssue({
233
+ code: z.ZodIssueCode.custom,
234
+ message: "Must not match schema in not",
235
+ });
236
+ }
237
+ });
238
+ }
239
+
240
+ return composedType;
148
241
  }
149
242
 
150
- function getTypeFunction(schema: JSONSchema7): ZodType<unknown> {
151
- if (!schema.type || !isValidSchemaType(schema.type as string)) {
152
- throw new Error(`Unsupported type: ${schema.type}`);
243
+ function matchesSchemaDefinition(schema: JSONSchema7Definition, value: unknown): boolean {
244
+ if (!isJSONSchema7(schema)) {
245
+ return schema;
246
+ }
247
+
248
+ return jsonSchemaDefinitionToZod(schema).safeParse(value).success;
249
+ }
250
+
251
+ function addSchemaIssues(
252
+ schema: JSONSchema7Definition,
253
+ value: unknown,
254
+ context: RefinementCtx
255
+ ): void {
256
+ if (!isJSONSchema7(schema)) {
257
+ if (!schema) {
258
+ context.addIssue({
259
+ code: z.ZodIssueCode.custom,
260
+ message: "Schema condition rejected the current value",
261
+ });
262
+ }
263
+ return;
264
+ }
265
+
266
+ const result = jsonSchemaDefinitionToZod(schema).safeParse(value);
267
+
268
+ if (result.success) {
269
+ return;
270
+ }
271
+
272
+ for (const issue of result.error.issues) {
273
+ context.addIssue(issue);
153
274
  }
154
- return typeMap[schema.type as SchemaType](schema);
275
+ }
276
+
277
+ function collectComposablePropertyKeys(schema: JSONSchema7): Set<string> {
278
+ const extendedSchema = asExtendedJSONSchema7(schema);
279
+ const keys = new Set<string>();
280
+
281
+ const visit = (definition?: JSONSchema7Definition): void => {
282
+ if (!definition || !isJSONSchema7(definition)) {
283
+ return;
284
+ }
285
+
286
+ const extendedDefinition = asExtendedJSONSchema7(definition);
287
+
288
+ if (extendedDefinition.properties) {
289
+ for (const key of Object.keys(extendedDefinition.properties)) {
290
+ keys.add(key);
291
+ }
292
+ }
293
+
294
+ if (extendedDefinition.required) {
295
+ for (const key of extendedDefinition.required) {
296
+ keys.add(key);
297
+ }
298
+ }
299
+
300
+ if (extendedDefinition.dependentRequired) {
301
+ for (const [key, requiredKeys] of Object.entries(extendedDefinition.dependentRequired)) {
302
+ keys.add(key);
303
+
304
+ for (const requiredKey of requiredKeys) {
305
+ keys.add(requiredKey);
306
+ }
307
+ }
308
+ }
309
+
310
+ if (extendedDefinition.dependentSchemas) {
311
+ for (const [key, dependentSchema] of Object.entries(extendedDefinition.dependentSchemas)) {
312
+ keys.add(key);
313
+ visit(dependentSchema);
314
+ }
315
+ }
316
+
317
+ if (extendedDefinition.anyOf) {
318
+ for (const item of extendedDefinition.anyOf) {
319
+ visit(item);
320
+ }
321
+ }
322
+
323
+ if (extendedDefinition.allOf) {
324
+ for (const item of extendedDefinition.allOf) {
325
+ visit(item);
326
+ }
327
+ }
328
+
329
+ visit(extendedDefinition.not);
330
+ visit(extendedDefinition.if);
331
+ visit(extendedDefinition.then);
332
+ visit(extendedDefinition.else);
333
+ };
334
+
335
+ visit(extendedSchema);
336
+
337
+ return keys;
338
+ }
339
+
340
+ function hasComposableValidation(schema: JSONSchema7): boolean {
341
+ const extendedSchema = asExtendedJSONSchema7(schema);
342
+ return Boolean(
343
+ extendedSchema.dependentRequired ||
344
+ extendedSchema.dependentSchemas ||
345
+ extendedSchema.if ||
346
+ extendedSchema.then ||
347
+ extendedSchema.else ||
348
+ (Array.isArray(extendedSchema.allOf) && extendedSchema.allOf.length > 0)
349
+ );
350
+ }
351
+
352
+ function createPrimitiveType(schema: JSONSchema7, type: Exclude<SchemaType, "array" | "object">): ZodType<unknown> {
353
+ switch (type) {
354
+ case "string":
355
+ if (schema.format && formatMap[schema.format]) {
356
+ return formatMap[schema.format](schema);
357
+ }
358
+ return z.string();
359
+ case "number":
360
+ if (schema.format === "percent") {
361
+ return percent;
362
+ }
363
+ return z.number();
364
+ case "integer":
365
+ return z.number().int();
366
+ case "boolean":
367
+ return z.boolean();
368
+ case "null":
369
+ return z.null();
370
+ }
371
+ }
372
+
373
+ function buildObjectSchema(schema: JSONSchema7): ZodType<unknown> {
374
+ const extendedSchema = asExtendedJSONSchema7(schema);
375
+ const shape: ZodRawShape = {};
376
+
377
+ if (extendedSchema.properties) {
378
+ for (const [key, propertySchema] of Object.entries(extendedSchema.properties)) {
379
+ if (!isJSONSchema7(propertySchema)) {
380
+ continue;
381
+ }
382
+
383
+ shape[key] = convertPropertyToZod(propertySchema, key, extendedSchema);
384
+ }
385
+ }
386
+
387
+ if (extendedSchema.required) {
388
+ for (const key of extendedSchema.required) {
389
+ if (!shape[key]) {
390
+ shape[key] = createRequiredUnknownSchema();
391
+ }
392
+ }
393
+ }
394
+
395
+ for (const key of collectComposablePropertyKeys(extendedSchema)) {
396
+ if (!shape[key]) {
397
+ shape[key] = z.unknown().optional();
398
+ }
399
+ }
400
+
401
+ const baseObject = z.object(shape);
402
+
403
+ if (!hasComposableValidation(extendedSchema)) {
404
+ return baseObject;
405
+ }
406
+
407
+ return baseObject.superRefine((value, context) => {
408
+ if (extendedSchema.dependentRequired) {
409
+ for (const [triggerKey, requiredKeys] of Object.entries(extendedSchema.dependentRequired)) {
410
+ if (!Object.prototype.hasOwnProperty.call(value, triggerKey)) {
411
+ continue;
412
+ }
413
+
414
+ for (const requiredKey of requiredKeys) {
415
+ if (!Object.prototype.hasOwnProperty.call(value, requiredKey)) {
416
+ context.addIssue({
417
+ code: z.ZodIssueCode.custom,
418
+ message: `Property "${requiredKey}" is required when "${triggerKey}" is present`,
419
+ path: [requiredKey],
420
+ });
421
+ }
422
+ }
423
+ }
424
+ }
425
+
426
+ if (extendedSchema.dependentSchemas) {
427
+ for (const [triggerKey, dependentSchema] of Object.entries(extendedSchema.dependentSchemas)) {
428
+ if (!Object.prototype.hasOwnProperty.call(value, triggerKey)) {
429
+ continue;
430
+ }
431
+
432
+ addSchemaIssues(dependentSchema, value, context);
433
+ }
434
+ }
435
+
436
+ if (extendedSchema.if) {
437
+ const matches = matchesSchemaDefinition(extendedSchema.if, value);
438
+
439
+ if (matches && extendedSchema.then) {
440
+ addSchemaIssues(extendedSchema.then, value, context);
441
+ }
442
+
443
+ if (!matches && extendedSchema.else) {
444
+ addSchemaIssues(extendedSchema.else, value, context);
445
+ }
446
+ }
447
+
448
+ if (extendedSchema.allOf) {
449
+ for (const item of extendedSchema.allOf) {
450
+ addSchemaIssues(item, value, context);
451
+ }
452
+ }
453
+ });
454
+ }
455
+
456
+ function buildArraySchema(schema: JSONSchema7): ZodType<unknown> {
457
+ if (!schema.items || Array.isArray(schema.items)) {
458
+ throw new Error("Invalid array items");
459
+ }
460
+
461
+ return applyValidators(z.array(jsonSchemaDefinitionToZod(schema.items, true)), schema, true);
462
+ }
463
+
464
+ function jsonSchemaDefinitionToZod(schema: JSONSchema7Definition, required = true): ZodType<unknown> {
465
+ if (!isJSONSchema7(schema)) {
466
+ return schema ? z.any() : z.never();
467
+ }
468
+
469
+ const inferredType = inferSchemaType(schema);
470
+ let zodType: ZodType<unknown>;
471
+
472
+ if (inferredType === "object") {
473
+ zodType = buildObjectSchema(schema);
474
+ return applyLogicalCompositionValidators(zodType, schema);
475
+ }
476
+
477
+ if (inferredType === "array") {
478
+ zodType = buildArraySchema(schema);
479
+ return applyLogicalCompositionValidators(zodType, schema);
480
+ }
481
+
482
+ if (inferredType) {
483
+ zodType = applyValidators(
484
+ createPrimitiveType(schema, inferredType as Exclude<SchemaType, "array" | "object">),
485
+ schema,
486
+ required
487
+ );
488
+ return applyLogicalCompositionValidators(zodType, schema);
489
+ }
490
+
491
+ zodType = applyValidators(z.any(), schema, required);
492
+ return applyLogicalCompositionValidators(zodType, schema);
155
493
  }
156
494
 
157
495
  /**
@@ -163,18 +501,35 @@ function convertPropertyToZod(
163
501
  parentSchema: JSONSchema7
164
502
  ): ZodType<unknown> {
165
503
  const required = Array.isArray(parentSchema.required) && parentSchema.required.includes(key);
166
-
167
- if (schema.type === 'object') {
168
- const objectSchema = z.object(jsonSchemaToZod(schema));
169
- return required ? objectSchema : objectSchema.optional();
504
+ const propertySchema = jsonSchemaDefinitionToZod(schema, required);
505
+
506
+ return required ? propertySchema : propertySchema.optional();
507
+ }
508
+
509
+ /**
510
+ * Converts a JSON Schema to a full Zod schema.
511
+ * This export should be preferred when the schema uses allOf or if/then/else.
512
+ */
513
+ export function jsonSchemaToZodSchema(
514
+ schema: JSONSchema7 | JSONSchema7Definition[],
515
+ ): ZodType<unknown> {
516
+ if (Array.isArray(schema)) {
517
+ const definitions = schema
518
+ .map(unwrapSchemaDefinition)
519
+ .filter((definition): definition is JSONSchema7Definition => definition !== null);
520
+
521
+ return jsonSchemaDefinitionToZod({
522
+ type: "object",
523
+ allOf: definitions,
524
+ });
170
525
  }
171
526
 
172
- const typeValidator = applyValidators(getTypeFunction(schema), schema, required);
173
- return required ? typeValidator : typeValidator.optional();
527
+ return jsonSchemaDefinitionToZod(schema);
174
528
  }
175
529
 
176
530
  /**
177
- * Converts a JSON Schema to a Zod schema
531
+ * Converts a JSON Schema to a Zod shape.
532
+ * This is kept for compatibility with the previous API.
178
533
  */
179
534
  export function jsonSchemaToZod(
180
535
  schema: JSONSchema7 | JSONSchema7Definition[],
@@ -182,13 +537,16 @@ export function jsonSchemaToZod(
182
537
  const zodSchema: Record<string, ZodType<unknown>> = {};
183
538
 
184
539
  if (Array.isArray(schema)) {
185
- // Handle array of schemas (merge all schemas)
186
540
  for (const item of schema) {
187
- if (!isJSONSchema7(item)) continue;
188
- Object.assign(zodSchema, jsonSchemaToZod(item));
541
+ const definition = unwrapSchemaDefinition(item);
542
+
543
+ if (!definition || !isJSONSchema7(definition)) {
544
+ continue;
545
+ }
546
+
547
+ Object.assign(zodSchema, jsonSchemaToZod(definition));
189
548
  }
190
- } else if (schema.type === 'object' && schema.properties) {
191
- // Handle object schema
549
+ } else if (schema.type === "object" && schema.properties) {
192
550
  for (const [key, prop] of Object.entries(schema.properties)) {
193
551
  if (!isJSONSchema7(prop)) continue;
194
552
  zodSchema[key] = convertPropertyToZod(prop, key, schema);
@@ -196,4 +554,4 @@ export function jsonSchemaToZod(
196
554
  }
197
555
 
198
556
  return zodSchema;
199
- }
557
+ }