메타데이터와 레지스트리
문서화, 코드 생성, AI 구조화 출력, 폼 검증 등의 목적으로 스키마에 추가 메타데이터를 연결하면 유용한 경우가 많습니다.
레지스트리
Zod의 메타데이터는 레지스트리를 통해 처리합니다. 레지스트리는 각각 엄격한 타입을 갖는 메타데이터와 연결된 스키마의 모음입니다. 간단한 레지스트리는 다음과 같이 만듭니다.
import * as z from "zod";
const myRegistry = z.registry<{ description: string }>();
이 레지스트리에 스키마를 등록·조회·제거하려면 다음과 같이 합니다.
const mySchema = z.string();
myRegistry.add(mySchema, { description: "A cool schema!"});
myRegistry.has(mySchema); // => true
myRegistry.get(mySchema); // => { description: "A cool schema!" }
myRegistry.remove(mySchema);
myRegistry.clear(); // wipe registry
TypeScript는 각 스키마의 메타데이터가 레지스트리의 메타데이터 타입과 일치하도록 강제합니다.
myRegistry.add(mySchema, { description: "A cool schema!" }); // ✅
myRegistry.add(mySchema, { description: 123 }); // ❌
id의 특수 처리 — Zod 레지스트리는id속성을 특별하게 처리합니다. 같은 식별자로 여러 스키마를 등록하면Error가 발생합니다. 즉id값은 중복될 수 없습니다. 전역 레지스트리를 포함한 모든 레지스트리에 적용됩니다.
.register()
참고 — 이 메서드는 새 스키마를 반환하지 않고 원래 스키마를 반환한다는 점에서 특별합니다. 다른 Zod 메서드에는 이런 동작이 없습니다. 아래에서 설명하는
.meta()와.describe()도 새 인스턴스를 반환합니다.
스키마는 레지스트리에 더 편리하게 자신을 추가할 수 있도록 .register() 메서드를 제공합니다.
const mySchema = z.string();
mySchema.register(myRegistry, { description: "A cool schema!" });
// => mySchema
이를 사용하면 스키마에서 메타데이터를 "인라인"으로 정의할 수 있습니다.
const mySchema = z.object({
name: z.string().register(myRegistry, { description: "The user's name" }),
age: z.number().register(myRegistry, { description: "The user's age" }),
})
메타데이터
z.globalRegistry
편의를 위해 Zod는 JSON Schema 생성 등의 목적으로 메타데이터를 저장할 수 있는 전역 레지스트리(z.globalRegistry)를 제공합니다. 이 레지스트리에는 다음 메타데이터 필드를 저장할 수 있습니다.
export interface GlobalMeta {
id?: string ;
title?: string ;
description?: string;
deprecated?: boolean;
[k: string]: unknown;
}
스키마의 메타데이터를 z.globalRegistry에 등록하려면 다음과 같이 합니다.
import * as z from "zod";
const emailSchema = z.email().register(z.globalRegistry, {
id: "email_address",
title: "Email address",
description: "Your email address",
examples: ["first.last@example.com"]
});
GlobalMeta 인터페이스를 전역으로 확장하려면 선언 병합을 사용합니다. 코드베이스의 어디에든 다음 내용을 추가하세요. 프로젝트 루트에 zod.d.ts 파일을 만드는 방식이 일반적입니다.
declare module "zod" {
interface GlobalMeta {
// add new fields here
examples?: unknown[];
}
}
// forces TypeScript to consider the file a module
export {}
.meta()
더 편리한 방법으로 .meta() 메서드를 사용해 스키마를 z.globalRegistry에 등록할 수 있습니다.
- Zod
- Zod Mini
const emailSchema = z.email().meta({
id: "email_address",
title: "Email address",
description: "Please enter a valid email address",
});
const emailSchema = z.email().check(
z.meta({
id: "email_address",
title: "Email address",
description: "Please enter a valid email address",
})
);
인수 없이 .meta()를 호출하면 스키마의 메타데이터를 가져옵니다.
emailSchema.meta();
// => { id: "email_address", title: "Email address", ... }
메타데이터는 특정 스키마 인스턴스에 연결됩니다. Zod 메서드는 불변이며 항상 새 인스턴스를 반환하므로 이 점을 기억해야 합니다.
const A = z.string().meta({ description: "A cool string" });
A.meta(); // => { description: "A cool string" }
const B = A.refine(_ => true);
B.meta(); // => undefined
.describe()
.describe() 메서드는 스키마를 z.globalRegistry에 등록하되 description 필드만 사용하는 축약형입니다.
- Zod
- Zod Mini
const emailSchema = z.email();
emailSchema.describe("An email address");
// equivalent to
emailSchema.meta({ description: "An email address" });
const emailSchema = z.email().check(z.describe("An email address"));
// equivalent to
z.email().check(z.meta({ description: "An email address" }));
사용자 지정 레지스트리
앞에서 사용자 지정 레지스트리의 간단한 예시를 살펴봤습니다.
import * as z from "zod";
const myRegistry = z.registry<{ description: string };>();
이제 더 고급 패턴을 살펴보겠습니다.
추론된 타입 참조하기
메타데이터 타입이 스키마의 추론된 타입을 참조하면 유용한 경우가 많습니다. 예를 들어 examples 필드에 스키마 출력의 예시를 담고 싶을 수 있습니다.
import * as z from "zod";
type MyMeta = { examples: z.$output[] };
const myRegistry = z.registry<MyMeta>();
myRegistry.add(z.string(), { examples: ["hello", "world"] });
myRegistry.add(z.number(), { examples: [1, 2, 3] });
특수 기호 z.$output은 스키마의 추론된 출력 타입(z.infer<typeof schema>)을 참조합니다. 마찬가지로 z.$input을 사용해 입력 타입을 참조할 수 있습니다.
스키마 타입 제한하기
레지스트리에 추가할 수 있는 스키마 타입을 제한하려면 z.registry()에 두 번째 제네릭을 전달합니다. 다음 레지스트리는 문자열 스키마만 허용합니다.
import * as z from "zod";
const myRegistry = z.registry<{ description: string }, z.ZodString>();
myRegistry.add(z.string(), { description: "A number" }); // ✅
myRegistry.add(z.number(), { description: "A number" }); // ❌
// ^ 'ZodNumber' is not assignable to parameter of type 'ZodString'