diff --git a/src/_vendor/zod-to-json-schema/parseDef.ts b/src/_vendor/zod-to-json-schema/parseDef.ts index f4dd747ca..fe0a0b6ff 100644 --- a/src/_vendor/zod-to-json-schema/parseDef.ts +++ b/src/_vendor/zod-to-json-schema/parseDef.ts @@ -131,7 +131,10 @@ const get$ref = ( // `["#","definitions","contactPerson","properties","person1","properties","name"]` // then we'll extract it out to `contactPerson_properties_person1_properties_name` case 'extract-to-root': - const name = item.path.slice(refs.basePath.length + 1).join('_'); + const name = item.path + .slice(refs.basePath.length + 1) + .map((segment) => segment.replace(/\s+/g, '_')) + .join('_'); // we don't need to extract the root schema in this case, as it's already // been added to the definitions diff --git a/tests/helpers/zod.test.ts b/tests/helpers/zod.test.ts index 1bc766376..7c2faca93 100644 --- a/tests/helpers/zod.test.ts +++ b/tests/helpers/zod.test.ts @@ -359,4 +359,33 @@ describe.each([ expect(consoleSpy).toHaveBeenCalledTimes(0); }); + + it('sanitizes property names with spaces in $ref values', () => { + const Thing = z.object({ id: z.string() }); + const Root = z.object({ + group: z.object({ + 'Thing With Spaces': Thing, + AnotherUsage: Thing, + }), + }); + + const result = zodResponseFormat(Root, 'example-scope'); + const schema = result.json_schema.schema; + + // Check definitions keys (draft-07 uses "definitions" not "$defs") + const defs = (schema as any).definitions || (schema as any).$defs || {}; + const defsKeys = Object.keys(defs); + const defsWithSpaces = defsKeys.filter((key: string) => key.includes(' ')); + expect(defsWithSpaces).toEqual([]); + + // Check all $ref values don't contain spaces + const schemaStr = JSON.stringify(schema); + const refMatches = schemaStr.match(/"\$ref"\s*:\s*"([^"]+)"/g) || []; + + refMatches.forEach((match) => { + const refValue = match.match(/"\$ref"\s*:\s*"([^"]+)"/)?.[1]; + expect(refValue).toBeDefined(); + expect(refValue).not.toContain(' '); + }); + }); });