Skip to content

Commit 6b13cc9

Browse files
committed
Polish JSON Schema for patterns
1 parent 39d84d0 commit 6b13cc9

File tree

2 files changed

+53
-41
lines changed

2 files changed

+53
-41
lines changed

packages/zod/src/v4/classic/tests/json-schema.test.ts

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -361,54 +361,64 @@ describe("toJSONSchema", () => {
361361
expect(z.toJSONSchema(z.string().regex(/asdf/))).toMatchInlineSnapshot(`
362362
{
363363
"$schema": "https://json-schema.org/draft/2020-12/schema",
364-
"format": "regex",
365364
"pattern": "asdf",
366365
"type": "string",
367366
}
368367
`);
369368
});
370369

371370
test("string patterns", () => {
372-
expect(z.toJSONSchema(z.string().startsWith("hello").includes("cruel").endsWith("world"))).toMatchInlineSnapshot(`
373-
{
374-
"$schema": "https://json-schema.org/draft/2020-12/schema",
375-
"allOf": [
376-
{
377-
"pattern": "^hello.*",
378-
},
379-
{
380-
"pattern": "cruel",
381-
},
371+
expect(
372+
z.toJSONSchema(z.string().startsWith("hello").includes("cruel").endsWith("world").regex(/stuff/))
373+
).toMatchInlineSnapshot(`
382374
{
383-
"pattern": ".*world$",
384-
},
385-
],
386-
"type": "string",
387-
}
388-
`);
375+
"$schema": "https://json-schema.org/draft/2020-12/schema",
376+
"allOf": [
377+
{
378+
"pattern": "^hello.*",
379+
},
380+
{
381+
"pattern": "cruel",
382+
},
383+
{
384+
"pattern": ".*world$",
385+
},
386+
{
387+
"pattern": "stuff",
388+
},
389+
],
390+
"type": "string",
391+
}
392+
`);
389393

390394
expect(
391-
z.toJSONSchema(
392-
z
393-
.string()
394-
.regex(/^hello/)
395-
.regex(/world$/)
396-
)
395+
z.toJSONSchema(z.string().startsWith("hello").includes("cruel").endsWith("world").regex(/stuff/), {
396+
target: "draft-7",
397+
})
397398
).toMatchInlineSnapshot(`
398-
{
399-
"$schema": "https://json-schema.org/draft/2020-12/schema",
400-
"allOf": [
401-
{
402-
"pattern": "^hello",
403-
},
404399
{
405-
"pattern": "world$",
406-
},
407-
],
408-
"format": "regex",
409-
"type": "string",
410-
}
411-
`);
400+
"$schema": "http://json-schema.org/draft-07/schema#",
401+
"allOf": [
402+
{
403+
"pattern": "^hello.*",
404+
"type": "string",
405+
},
406+
{
407+
"pattern": "cruel",
408+
"type": "string",
409+
},
410+
{
411+
"pattern": ".*world$",
412+
"type": "string",
413+
},
414+
{
415+
"pattern": "stuff",
416+
"type": "string",
417+
},
418+
],
419+
"type": "string",
420+
}
421+
`);
412422
});
413423

414424
test("number constraints", () => {

packages/zod/src/v4/core/to-json-schema.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ interface EmitParams {
4848
| undefined;
4949
}
5050

51-
const formatMap: Partial<Record<checks.$ZodStringFormats, string>> = {
51+
const formatMap: Partial<Record<checks.$ZodStringFormats, string | undefined>> = {
5252
guid: "uuid",
5353
url: "uri",
5454
datetime: "date-time",
5555
json_string: "json-string",
56+
regex: "", // do not set
5657
};
5758

5859
interface Seen {
@@ -156,15 +157,16 @@ export class JSONSchemaGenerator {
156157
// custom pattern overrides format
157158
if (format) {
158159
json.format = formatMap[format as checks.$ZodStringFormats] ?? format;
160+
if (json.format === "") delete json.format; // empty format is not valid
159161
}
160162
if (contentEncoding) json.contentEncoding = contentEncoding;
161163
if (patterns && patterns.size > 0) {
162164
const regexes = [...patterns];
163-
if (regexes.length === 1) {
164-
json.pattern = regexes[0].source;
165-
} else {
166-
json.allOf = [
165+
if (regexes.length === 1) json.pattern = regexes[0].source;
166+
else if (regexes.length > 1) {
167+
result.schema.allOf = [
167168
...regexes.map((regex) => ({
169+
...(this.target === "draft-7" ? { type: "string" } : {}),
168170
pattern: regex.source,
169171
})),
170172
];

0 commit comments

Comments
 (0)