기본 사용법
이 페이지에서는 스키마 생성, 데이터 파싱, 추론된 타입 사용의 기본 사항을 살펴봅니다. Zod 스키마 API의 전체 문서는 스키마 정의하기를 참조하세요.
스키마 정의하기
먼저 스키마를 정의해야 합니다. 이 가이드에서는 간단한 객체 스키마를 사용합니다.
- Zod
- Zod Mini
import * as z from "zod";
const Player = z.object({
username: z.string(),
xp: z.number()
});
import * as z from "zod/mini"
const Player = z.object({
username: z.string(),
xp: z.number()
});
데이터 파싱하기
Zod 스키마로 입력을 검증하려면 .parse를 사용합니다. 입력이 유효하면 Zod는 타입이 보장된 입력의 깊은 복사본을 반환합니다.
Player.parse({ username: "billie", xp: 100 });
// => returns { username: "billie", xp: 100 }
오류 처리하기
검증이 실패하면 .parse() 메서드는 검증 이슈에 대한 세부 정보가 담긴 ZodError 인스턴스를 던집니다.
- Zod
- Zod Mini
try {
Player.parse({ username: 42, xp: "100" });
} catch(error){
if(error instanceof z.ZodError){
error.issues;
/* [
{
expected: 'string',
code: 'invalid_type',
path: [ 'username' ],
message: 'Invalid input: expected string'
},
{
expected: 'number',
code: 'invalid_type',
path: [ 'xp' ],
message: 'Invalid input: expected number'
}
] */
}
}
try {
Player.parse({ username: 42, xp: "100" });
} catch(error){
if(error instanceof z.core.$ZodError){
error.issues;
/* [
{
expected: 'string',
code: 'invalid_type',
path: [ 'username' ],
message: 'Invalid input: expected string'
},
{
expected: 'number',
code: 'invalid_type',
path: [ 'xp' ],
message: 'Invalid input: expected number'
}
] */
}
}
try/catch 블록을 피하려면 .safeParse() 메서드를 사용해 파싱에 성공한 데이터 또는 ZodError를 담은 일반 결과 객체를 받을 수 있습니다. 결과 타입은 판별 유니온이므로 두 경우를 편리하게 처리할 수 있습니다.
const result = Player.safeParse({ username: 42, xp: "100" });
if (!result.success) {
result.error; // ZodError instance
} else {
result.data; // { username: string; xp: number }
}
입력이 유효한지만 확인하면 될 때는 최상위 z.validate() 함수를 사용합니다. 이 함수는 불리언을 반환하고 오류를 생성하지 않으며, 스키마 입력 타입의 타입 가드로 작동합니다. 유효하지 않은 입력에서는 .safeParse().success보다 최대 16배 빠릅니다.
z.validate(Player, { username: "billie", xp: 100 }); // true
z.validate(Player, { username: 42, xp: "100" }); // false
비동기 세부 검증이나 변환이 있는 스키마에는 z.validateAsync()를 사용합니다. 컴파일된 스키마에서는 z.validate()가 컴파일된 빠른 경로에서 직접 결과를 반환합니다.
타입 추론하기
Zod는 스키마 정의에서 정적 타입을 추론합니다. z.infer<> 유틸리티로 이 타입을 추출해 원하는 방식으로 사용할 수 있습니다.
const Player = z.object({
username: z.string(),
xp: z.number()
});
// extract the inferred type
type Player = z.infer<typeof Player>;
// use it in your code
const player: Player = { username: "billie", xp: 100 };
경우에 따라 스키마의 입력 타입과 출력 타입이 서로 다를 수 있습니다. 예를 들어 .transform() API는 입력을 한 타입에서 다른 타입으로 변환할 수 있습니다. 이 경우 입력 타입과 출력 타입을 각각 추출할 수 있습니다.
const mySchema = z.string().transform((val) => val.length);
type MySchemaIn = z.input<typeof mySchema>;
// => string
type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema>
// number
기존 타입과 일치시키기
데이터베이스 모델, 생성된 클라이언트의 타입, 직접 소유하지 않은 인터페이스처럼 타입이 먼저 존재하는 경우가 있습니다. 이를 z.toZod<T>()에 전달하면 TypeScript가 스키마의 출력 타입이 정확히 T인지 검사합니다.
type Player = {
username: string;
xp: number;
};
const Player = z.toZod<Player>()(
z.object({
username: z.string(),
xp: z.number(),
})
);
Player.shape.username; // ZodString — the schema is returned unchanged
이 검사는 타입의 정확한 동등성을 확인하므로 타입에서 조금이라도 벗어나면 컴파일 오류가 발생합니다.
z.toZod<Player>()(
z.object({
username: z.string(),
xp: z.number(),
admin: z.boolean(), // ❌ extra key
})
);
일반적인 대안인 satisfies z.ZodType<Player>는 할당 가능 여부만 검사합니다. 누락된 필수 키는 찾아내지만, 추가 키나 생략된 선택적 키, 그 자체로 사용한 z.any()는 모두 통과합니다.
z.object({
username: z.string(),
xp: z.number(),
admin: z.boolean(),
}) satisfies z.ZodType<Player>; // ✅ no error
z.any() satisfies z.ZodType<Player>; // ✅ no error
정확성 검사는 TypeScript로 작성한 타입의 형태를 그대로 기준으로 합니다. 따라서 교차 타입 대상은 .and()와 일치하며 .safeExtend()와는 일치하지 않습니다.
type Entry = { id: string } & { label: string };
z.toZod<Entry>()(z.object({ id: z.string() }).and(z.object({ label: z.string() }))); // ✅
z.toZod<Entry>()(z.object({ id: z.string() }).safeExtend({ label: z.string() })); // ❌
대상 타입을 평탄화하면 .safeExtend()와 일치합니다.
type Flatten<T> = { [K in keyof T]: T[K] } & {};
z.toZod<Flatten<Entry>>()(z.object({ id: z.string() }).safeExtend({ label: z.string() })); // ✅
이제 기본 사항을 살펴봤으므로 스키마 API로 넘어가겠습니다.