본문으로 건너뛰기

메타데이터와 레지스트리

문서화, 코드 생성, 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에 등록할 수 있습니다.

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 필드만 사용하는 축약형입니다.

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'