From 75c84633ee5850b72274fcea14aae6eeb55ad9e0 Mon Sep 17 00:00:00 2001 From: unnoq Date: Sat, 1 Nov 2025 16:43:15 +0700 Subject: [PATCH] Fix: Correct handling of union schemas The previous implementation incorrectly simplified `anyOf` schemas with only one element. This commit removes that simplification. Additionally, the `structureDepth` was not being incremented when converting union and intersection schemas. This has been fixed to prevent stack overflows on complex schemas. --- packages/zod/src/converter.test.ts | 2 +- packages/zod/src/converter.ts | 8 ++------ packages/zod/src/zod4/converter.combination.test.ts | 2 +- packages/zod/src/zod4/converter.ts | 6 +++--- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/zod/src/converter.test.ts b/packages/zod/src/converter.test.ts index 1c87f33c5..aa93e52a3 100644 --- a/packages/zod/src/converter.test.ts +++ b/packages/zod/src/converter.test.ts @@ -212,7 +212,7 @@ const combinationCases: SchemaTestCase[] = [ }, { schema: z.union([z.string(), z.undefined()]), - input: [false, { type: 'string' }], + input: [false, { anyOf: [{ type: 'string' }] }], }, { schema: z.intersection(z.string(), z.number()), diff --git a/packages/zod/src/converter.ts b/packages/zod/src/converter.ts index 4fb9378e4..7f0f5221d 100644 --- a/packages/zod/src/converter.ts +++ b/packages/zod/src/converter.ts @@ -513,7 +513,7 @@ export class ZodToJsonSchemaConverter implements ConditionalSchemaConverter { let required = true for (const item of schema_._def.options) { - const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth) + const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth + 1) if (!itemRequired) { required = false @@ -527,10 +527,6 @@ export class ZodToJsonSchemaConverter implements ConditionalSchemaConverter { } } - if (anyOf.length === 1) { - return [required, anyOf[0]!] - } - return [required, { anyOf }] } @@ -541,7 +537,7 @@ export class ZodToJsonSchemaConverter implements ConditionalSchemaConverter { let required: boolean = false for (const item of [schema_._def.left, schema_._def.right]) { - const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth) + const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth + 1) allOf.push(itemJson) diff --git a/packages/zod/src/zod4/converter.combination.test.ts b/packages/zod/src/zod4/converter.combination.test.ts index 1f794c99f..8edd3707e 100644 --- a/packages/zod/src/zod4/converter.combination.test.ts +++ b/packages/zod/src/zod4/converter.combination.test.ts @@ -15,7 +15,7 @@ testSchemaConverter([ { name: 'union([z.string(), z.undefined()])', schema: z.union([z.string(), z.undefined()]), - input: [false, { type: 'string' }], + input: [false, { anyOf: [{ type: 'string' }] }], }, { name: 'intersection(z.string(), z.number())', diff --git a/packages/zod/src/zod4/converter.ts b/packages/zod/src/zod4/converter.ts index 3b822bc10..d25852a7f 100644 --- a/packages/zod/src/zod4/converter.ts +++ b/packages/zod/src/zod4/converter.ts @@ -325,7 +325,7 @@ export class ZodToJsonSchemaConverter implements ConditionalSchemaConverter { let required = true for (const item of union._zod.def.options) { - const [itemRequired, itemJson] = this.#convert(item, options, lazyDepth, structureDepth) + const [itemRequired, itemJson] = this.#convert(item, options, lazyDepth, structureDepth + 1) if (!itemRequired) { required = false @@ -343,7 +343,7 @@ export class ZodToJsonSchemaConverter implements ConditionalSchemaConverter { } } - return [required, anyOf.length === 1 ? anyOf[0]! : { anyOf }] + return [required, { anyOf }] } case 'intersection': { @@ -353,7 +353,7 @@ export class ZodToJsonSchemaConverter implements ConditionalSchemaConverter { let required = false for (const item of [intersection._zod.def.left, intersection._zod.def.right]) { - const [itemRequired, itemJson] = this.#convert(item, options, lazyDepth, structureDepth) + const [itemRequired, itemJson] = this.#convert(item, options, lazyDepth, structureDepth + 1) json.allOf.push(itemJson)