JSON Schema
z.fromJSONSchema()
Zod는 JSON Schema를 Zod 스키마로 변환하는 z.fromJSONSchema()를 제공합니다.
import * as z from "zod";
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name", "age"],
};
const zodSchema = z.fromJSONSchema(jsonSchema);
z.toJSONSchema()
Zod 스키마를 JSON Schema로 변환하려면 z.toJSONSchema() 함수를 사용하세요.
import * as z from "zod";
const schema = z.object({
name: z.string(),
age: z.number(),
});
z.toJSONSchema(schema)
// => {
// type: 'object',
// properties: { name: { type: 'string' }, age: { type: 'number' } },
// required: [ 'name', 'age' ],
// additionalProperties: false,
// }
모든 스키마와 검사는 가장 유사한 JSON Schema 표현으로 변환됩니다. 일부 타입은 대응하는 표현이 없어 적절하게 나타낼 수 없습니다. 이런 사례를 처리하는 방법은 아래의 unrepresentable 섹션을 참조하세요.
z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌
z.number().multipleOf(0); // ❌ the divisor must be finite and non-zero
두 번째 인수를 사용해 변환 로직을 사용자 지정할 수 있습니다.
z.toJSONSchema(schema, {
// ...params
})
다음은 지원되는 각 매개변수에 대한 빠른 참고 자료입니다. 각 항목은 아래에서 더 자세히 설명합니다.
interface ToJSONSchemaParams {
/** The JSON Schema version to target.
* - `"draft-2020-12"` — Default. JSON Schema Draft 2020-12
* - `"draft-07"` — JSON Schema Draft 7
* - `"draft-04"` — JSON Schema Draft 4
* - `"openapi-3.0"` — OpenAPI 3.0 Schema Object */
target?:
| "draft-04"
| "draft-4"
| "draft-07"
| "draft-7"
| "draft-2020-12"
| "openapi-3.0"
| ({} & string)
| undefined;
/** A registry used to look up metadata for each schema.
* Any schema with an `id` property will be extracted as a $def. */
metadata?: $ZodRegistry<Record<string, any>>;
/** How to handle unrepresentable types.
* - `"throw"` — Default. Unrepresentable types throw an error
* - `"any"` — Unrepresentable types become `{}`
* - A function — returns the JSON Schema to use, or `"any"`/`"throw"` */
unrepresentable?:
| "throw"
| "any"
| ((ctx: {
zodSchema: $ZodTypes;
path: (string | number)[];
message: string;
}) => JSONSchema | "throw" | "any" | undefined);
/** How to handle cycles.
* - `"ref"` — Default. Cycles will be broken using $defs
* - `"throw"` — Cycles will throw an error if encountered */
cycles?: "ref" | "throw";
/* How to handle reused schemas.
* - `"inline"` — Default. Reused schemas will be inlined
* - `"ref"` — Reused schemas will be extracted as $defs */
reused?: "ref" | "inline";
/** A function used to convert `id` values to URIs to be used in *external* $refs.
*
* Default is `(id) => id`.
*/
uri?: (id: string) => string;
}
io
ZodPipe, ZodDefault, 강제 변환된 원시 타입처럼 입력과 출력 타입이 서로 다른 스키마 타입이 있습니다. 기본적으로 z.toJSONSchema의 결과는 출력 타입을 나타냅니다. 대신 입력 타입을 추출하려면 "io": "input"을 사용하세요.
const mySchema = z.string().transform(val => val.length).pipe(z.number());
// ZodPipe
const jsonSchema = z.toJSONSchema(mySchema);
// => { type: "number" }
const jsonSchema = z.toJSONSchema(mySchema, { io: "input" });
// => { type: "string" }
target
대상 JSON Schema 버전을 설정하려면 target 매개변수를 사용하세요. 기본적으로 Zod는 Draft 2020-12를 대상으로 합니다.
z.toJSONSchema(schema, { target: "draft-07" });
z.toJSONSchema(schema, { target: "draft-2020-12" });
z.toJSONSchema(schema, { target: "draft-04" });
z.toJSONSchema(schema, { target: "openapi-3.0" });
metadata
아직 읽지 않았다면 Zod에서 메타데이터를 저장하는 방법을 이해하기 위해 메타데이터와 레지스트리 페이지를 읽어보세요.
Zod에서 메타데이터는 레지스트리에 저장됩니다. Zod는 전역 레지스트리 z.globalRegistry를 내보내며, 이를 사용해 id, title, description, examples 같은 일반적인 메타데이터 필드를 저장할 수 있습니다.
- Zod
- Zod Mini
import * as z from "zod";
// `.meta()` is a convenience method for registering a schema in `z.globalRegistry`
const emailSchema = z.string().meta({
title: "Email address",
description: "Your email address",
});
z.toJSONSchema(emailSchema);
// => { type: "string", title: "Email address", description: "Your email address", ... }
import * as z from "zod";
// `.meta()` is a convenience method for registering a schema in `z.globalRegistry`
const emailSchema = z.string().register(z.globalRegistry, {
title: "Email address",
description: "Your email address",
});
z.toJSONSchema(emailSchema);
// => { type: "string", title: "Email address", description: "Your email address", ... }
모든 메타데이터 필드는 결과 JSON Schema에 복사됩니다.
const schema = z.string().meta({
whatever: 1234
});
z.toJSONSchema(schema);
// => { type: "string", whatever: 1234 }
메타데이터는 Zod가 생성한 키워드보다 우선합니다.
z.toJSONSchema(z.string().meta({ type: "number" }));
// => { type: "number" }
z.toJSONSchema(z.date().meta({ type: "string", format: "date-time" }), { unrepresentable: "any" });
// => { type: "string", format: "date-time" }
메타데이터를 완전히 제외하려면 빈 레지스트리를 전달하세요: z.toJSONSchema(schema, { metadata: z.registry() }). 생성된 스키마를 수정하려면 override를 사용하세요.
unrepresentable
다음 API는 JSON Schema로 나타낼 수 없습니다. 기본적으로 Zod는 이러한 API를 만나면 오류를 던집니다. JSON에 대응하는 표현이 없으므로 JSON Schema로 변환하려는 시도는 안전하지 않습니다. 스키마를 수정해야 합니다. 다음 항목 중 하나라도 발견되면 오류가 발생합니다.
z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌
z.number().multipleOf(0); // ❌ the divisor must be finite and non-zero
기본적으로 Zod는 이 중 하나라도 발견하면 오류를 던집니다.
z.toJSONSchema(z.bigint());
// => throws Error
unrepresentable 옵션을 "any"로 설정하면 이 동작을 변경할 수 있습니다. 나타낼 수 없는 모든 타입을 {}로 변환하며, 이는 JSON Schema의 unknown에 해당합니다. 또한 나타낼 수 없는 검사는 해당 검사가 속한 스키마에서 제거합니다.
z.toJSONSchema(z.bigint(), { unrepresentable: "any" });
// => {}
z.toJSONSchema(z.number().multipleOf(0), { unrepresentable: "any" });
// => { type: "number" }
사례별로 결정하려면 대신 함수를 전달하세요. 이 함수는 나타낼 수 없는 스키마를 만날 때마다 호출됩니다. 대신 사용할 JSON Schema, "any" 또는 "throw"를 반환하세요. 아무것도 반환하지 않으면 기본적으로 오류를 던집니다.
z.toJSONSchema(z.object({ createdAt: z.date(), id: z.bigint() }), {
unrepresentable: ({ zodSchema }) =>
zodSchema._zod.def.type === "date" ? { type: "string", format: "date-time" } : "throw",
});
// => throws Error (BigInt cannot be represented in JSON Schema)
핸들러는 스키마의 path와 Zod가 던졌을 message도 받습니다. 핸들러가 던진 모든 오류는 그대로 전파되므로 나타낼 수 없는 타입을 원하는 문구로 보고할 수 있습니다.
z.toJSONSchema(schema, {
unrepresentable: ({ path, message }) => {
throw new Error(`${message} (at /${path.join("/")})`);
},
});
두 스키마가 같은 zodSchema를 가지면서 message는 다를 수 있습니다. 예를 들어 같은 리터럴의 undefined 멤버와 bigint 멤버는 모두 리터럴로 전달됩니다. 따라서 이 둘을 구분하려면 message를 기준으로 분기하세요.
JSON으로 직렬화할 수 없는 기본 값도 .default() 스키마로서 같은 핸들러를 거칩니다. "any"에서는 기본값이 제거되고 스키마의 나머지 부분은 평소처럼 출력됩니다. 자체 스키마를 제공하려면 JSON Schema를 반환하세요.
z.toJSONSchema(z.bigint().default(0n), {
unrepresentable: ({ zodSchema }) => {
const def = zodSchema._zod.def;
if (def.type === "bigint") return { type: "integer", format: "int64" };
if (def.type === "default") return { default: String(def.defaultValue) };
return "throw";
},
});
// => { type: "integer", format: "int64", default: "0" }
리터럴에 대해 JSON Schema를 반환하면 리터럴 전체가 대체되어 나타낼 수 있는 멤버도 제거됩니다. 즉 z.literal(["a", 1n])는 반환한 값만 남고 "a"는 사라집니다. 기존의 값별 동작을 유지하려면 대신 "any"를 반환하세요.
cycles
순환을 처리하는 방법입니다. z.toJSONSchema()가 스키마를 순회하다 순환을 만나면 $ref로 나타냅니다.
const User = z.object({
name: z.string(),
get friend() {
return User;
},
});
z.toJSONSchema(User);
// => {
// type: 'object',
// properties: { name: { type: 'string' }, friend: { '$ref': '#' } },
// required: [ 'name', 'friend' ],
// additionalProperties: false,
// }
대신 오류를 던지려면 cycles 옵션을 "throw"로 설정하세요.
z.toJSONSchema(User, { cycles: "throw" });
// => throws Error
reused
하나의 스키마에서 여러 번 재사용되는 하위 스키마를 처리하는 방법입니다. 기본적으로 Zod는 이러한 스키마를 인라인으로 삽입합니다.
const name = z.string();
const User = z.object({
firstName: name,
lastName: name,
});
z.toJSONSchema(User);
// => {
// type: 'object',
// properties: {
// firstName: { type: 'string' },
// lastName: { type: 'string' }
// },
// required: [ 'firstName', 'lastName' ],
// additionalProperties: false,
// }
대신 reused 옵션을 "ref"로 설정해 이러한 스키마를 $defs로 추출할 수 있습니다.
z.toJSONSchema(User, { reused: "ref" });
// => {
// type: 'object',
// properties: {
// firstName: { '$ref': '#/$defs/__schema0' },
// lastName: { '$ref': '#/$defs/__schema0' }
// },
// required: [ 'firstName', 'lastName' ],
// additionalProperties: false,
// '$defs': { __schema0: { type: 'string' } }
// }
override
사용자 지정 재정의 로직을 정의하려면 override를 사용하세요. 제공된 콜백은 원본 Zod 스키마와 기본 JSON Schema에 접근할 수 있습니다. 이 함수는 ctx.jsonSchema를 직접 수정해야 합니다.
const mySchema = /* ... */
z.toJSONSchema(mySchema, {
override: (ctx)=>{
ctx.zodSchema; // the original Zod schema
ctx.jsonSchema; // the default JSON Schema
// directly modify
ctx.jsonSchema.whatever = "sup";
}
});
나타낼 수 없는 타입은 이 함수가 호출되기 전에 Error를 던진다는 점에 유의하세요. 그중 하나를 나타내려면 unrepresentable을 사용하세요. 해당 핸들러는 다른 타입에 대한 오류를 비활성화하지 않고 특정 타입만 대체합니다. unrepresentable: "any"를 override와 함께 설정할 수도 있지만, 나타낼 수 없는 모든 타입이 제거됩니다.
// support z.date() as ISO datetime strings
const result = z.toJSONSchema(z.date(), {
unrepresentable: "any",
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if(def.type ==="date"){
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
}
},
});
변환
아래에서 Zod의 JSON Schema 변환 로직이 어떻게 동작하는지 더 자세히 살펴봅니다.
문자열 형식
Zod는 다음 스키마 타입을 이에 해당하는 JSON Schema format으로 변환합니다.
// Supported via `format`
z.email(); // => { type: "string", format: "email" }
z.iso.datetime(); // => { type: "string", format: "date-time" }
z.iso.date(); // => { type: "string", format: "date" }
z.iso.duration(); // => { type: "string", format: "duration" }
z.ipv4(); // => { type: "string", format: "ipv4" }
z.ipv6(); // => { type: "string", format: "ipv6" }
z.uuid(); // => { type: "string", format: "uuid" }
z.guid(); // => { type: "string", format: "uuid" }
z.url(); // => { type: "string", format: "uri" }
local: true 및 precision: -1을 사용한 z.iso.datetime() 변형은 pattern을 사용합니다. RFC 3339 date-time에는 오프셋과 초가 모두 필요하기 때문입니다.
다음 스키마는 contentEncoding으로 지원됩니다.
z.base64(); // => { type: "string", contentEncoding: "base64" }
그 밖의 모든 문자열 형식은 pattern으로 지원됩니다.
z.iso.time();
z.base64url();
z.cuid();
z.emoji();
z.nanoid();
z.cuid2();
z.ulid();
z.cidrv4();
z.cidrv6();
z.mac();
숫자 타입
Zod는 다음 숫자 타입을 JSON Schema로 변환합니다.
// number
z.number(); // => { type: "number" }
z.float32(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
z.float64(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
// integer
z.int(); // => { type: "integer" }
z.int32(); // => { type: "integer", exclusiveMinimum: ..., exclusiveMaximum: ... }
객체 스키마
기본적으로 z.object() 스키마에는 additionalProperties: "false"가 포함됩니다. 일반 z.object() 스키마는 추가 속성을 제거하므로 이는 Zod의 기본 동작을 정확하게 나타냅니다.
import * as z from "zod";
const schema = z.object({
name: z.string(),
age: z.number(),
});
z.toJSONSchema(schema)
// => {
// type: 'object',
// properties: { name: { type: 'string' }, age: { type: 'number' } },
// required: [ 'name', 'age' ],
// additionalProperties: false,
// }
"input" 모드에서 JSON Schema로 변환할 때는 additionalProperties가 설정되지 않습니다. 자세한 내용은 io 문서를 참조하세요.
import * as z from "zod";
const schema = z.object({
name: z.string(),
age: z.number(),
});
z.toJSONSchema(schema, { io: "input" });
// => {
// type: 'object',
// properties: { name: { type: 'string' }, age: { type: 'number' } },
// required: [ 'name', 'age' ],
// }
반면 다음과 같이 동작합니다.
z.looseObject()는additionalProperties: false를 절대로 설정하지 않습니다.z.strictObject()는additionalProperties: false를 항상 설정합니다.
파일 스키마
Zod는 z.file()을 다음과 같은 OpenAPI 호환 스키마로 변환합니다.
z.file();
// => { type: "string", format: "binary", contentEncoding: "binary" }
크기와 MIME 검사도 표현됩니다.
z.file().min(1).max(1024 * 1024).mime("image/png");
// => {
// type: "string",
// format: "binary",
// contentEncoding: "binary",
// contentMediaType: "image/png",
// minLength: 1,
// maxLength: 1048576,
// }
null 허용 여부
Zod는 z.null()을 JSON Schema의 { type: "null" }로 변환합니다.
z.null();
// => { type: "null" }
z.undefined()는 JSON Schema로 나타낼 수 없습니다(아래 참조).
마찬가지로 nullable은 허용되는 타입 집합에 "null"을 추가합니다. 단일 타입으로 작성할 수 없는 내부 스키마는 anyOf를 사용합니다.
z.nullable(z.string());
// => { type: ["string", "null"] }
z.nullable(z.string().min(5));
// => { anyOf: [{ type: "string", minLength: 5 }, { type: "null" }] }
선택적 스키마는 optional 주석이 추가되지만 그 외에는 그대로 표현됩니다.
z.optional(z.string());
// => { type: "string" }
레지스트리
z.toJSONSchema()에 스키마를 전달하면 다른 정의에 의존하지 않는 자체 완결형 JSON Schema를 반환합니다.
한편 Zod 스키마 집합을 서로 연결된 여러 JSON Schema로 나타내고 싶을 수도 있습니다. 예를 들어 이를 .json 파일로 저장해 웹 서버에서 제공할 수 있습니다.
import * as z from "zod";
const User = z.object({
name: z.string(),
get posts(){
return z.array(Post);
}
});
const Post = z.object({
title: z.string(),
content: z.string(),
get author(){
return User;
}
});
z.globalRegistry.add(User, {id: "User"});
z.globalRegistry.add(Post, {id: "Post"});
이를 위해 z.toJSONSchema()에 레지스트리를 전달할 수 있습니다.
중요 — 모든 스키마에는 레지스트리에 등록된
id속성이 있어야 합니다!id가 없는 스키마는 무시됩니다.
z.toJSONSchema(z.globalRegistry);
// => {
// schemas: {
// User: {
// id: 'User',
// type: 'object',
// properties: {
// name: { type: 'string' },
// posts: { type: 'array', items: { '$ref': 'Post' } }
// },
// required: [ 'name', 'posts' ],
// additionalProperties: false,
// },
// Post: {
// id: 'Post',
// type: 'object',
// properties: {
// title: { type: 'string' },
// content: { type: 'string' },
// author: { '$ref': 'User' }
// },
// required: [ 'title', 'content', 'author' ],
// additionalProperties: false,
// }
// }
// }
기본적으로 $ref URI는 "User" 같은 단순한 상대 경로입니다. 이를 절대 URI로 만들려면 uri 옵션을 사용하세요. 이 옵션은 id를 정규화된 절대 URI로 변환하는 함수를 받습니다.
z.toJSONSchema(z.globalRegistry, {
uri: (id) => `https://example.com/${id}.json`
});
// => {
// schemas: {
// User: {
// id: 'User',
// type: 'object',
// properties: {
// name: { type: 'string' },
// posts: {
// type: 'array',
// items: { '$ref': 'https://example.com/Post.json' }
// }
// },
// required: [ 'name', 'posts' ],
// additionalProperties: false,
// },
// Post: {
// id: 'Post',
// type: 'object',
// properties: {
// title: { type: 'string' },
// content: { type: 'string' },
// author: { '$ref': 'https://example.com/User.json' }
// },
// required: [ 'title', 'content', 'author' ],
// additionalProperties: false,
// }
// }
// }