typescript의 유효성 검증은 컴파일 시에만 발생.
실제로 자바스크립트 프로그램이 실행 될 때는 아무런 역할을 하지 못함.
⇒ zod의 유효성 검증은 컴파일이 끝난 프로그램의 실행 시점에서 발생
typescript는 결국 예측 가능한 문제를 개발자에게 알려줌으로써 좀 더 견고한 코드를 짤 수 있도록 도와줌.
유효성 검증은 프로그램 실행 시점에서 최종 사용자를 위하여 동작.
strict
모드 활성화.// tsconfig.json
{
"compilerOptions": {
"strict" : true
}
}
yarn add zod yarn add zod@canary
import { z } from "zod";
// primitive values
z.string();
z.number();
z.bigint();
z.boolean();
z.date();
z.symbol();
// object
z.object({
username: z.string()
});
// array
z.array(z.string())
z.string().array() // 💡BMS프로젝트 컨벤션!
// function
z.function();
// empty types
z.undefined();
z.null();
z.void(); // accepts undefined
// catch-all types
// allows any value
z.any();
z.unknown();
// never type
// allows no values
z.never();
z.coerce.string(); // String(input)
z.coerce.number(); // Number(input)
z.coerce.boolean(); // Boolean(input)
z.coerce.bigint(); // BigInt(input)
z.coerce.date(); // new Date(input)
자료형 데이터 스키마 선언 시 에러 메세지 지정 가능.
const name = z.string({
required_error: "Name is required",
invalid_type_error: "Name must be a string",
});
const age = z.number({
required_error: "Age is required",
invalid_type_error: "Age must be a number",
});
const isActive = z.boolean({
required_error: "isActive is required",
invalid_type_error: "isActive must be a boolean",
});
const myDateSchema = z.date({
required_error: "Please select a date and time",
invalid_type_error: "That's not a date!",
});
const isNaN = z.nan({
required_error: "isNaN is required",
invalid_type_error: "isNaN must be not a number",
});
import { z } from "zod";
// 'string' 스키마 생성
const mySchema = z.string();
// 기본 파싱 => 타입이 일치하지 않으면 에러
mySchema.parse("tuna"); // => "tuna"
mySchema.parse(12); // => throws ZodError
// "safe"파싱 => 유효성 검사 실패 시에도 오류를 발생시키지 않음.
mySchema.safeParse("tuna"); // => { success: true; data: "tuna" }
mySchema.safeParse(12); // => { success: false; error: ZodError }
parse()
: 스키마를 기준으로 데이터의 유효성 확인parseAsync()
: 비동기 정제를 사용하는 경우 parseAsync를 사용해야 함safeParse()
: 유효성 검사에 실패해도 오류를 던지지 않음.safeParseAsync()
: safeParse의 비동기버전. 편의상 .spa()
로도 사용 가능.// validations
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().email();
z.string().url();
z.string().emoji();
z.string().uuid();
z.string().cuid();
z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().datetime(); // ISO 8601
z.string().ip(); // 기본값은 IPv4 및 IPv6
// transformations
z.string().trim();
z.string().toLowerCase();
z.string().toUpperCase();
z.number().gt(5);
z.number().gte(5); // .min(5)
z.number().lt(5);
z.number().lte(5); // .max(5)
z.number().int(); // 정수
z.number().positive(); // > 0 / 양수
z.number().nonnegative(); // >= 0
z.number().negative(); // < 0 / 음수
z.number().nonpositive(); // <= 0
z.number().multipleOf(5); // 5의 배수
z.number().finite(); // 유한한 수. 무한대의 값이면 안됨.
z.number().safe(); // javascript에서의 안전한 정수값 이내의 수
z.date().min(new Date("1900-01-01"), { message: "Too old" });
z.date().max(new Date(), { message: "Too young!" });
z.string().email({ message: "Invalid email address" });
z.string().includes("tuna", { message: "Must include tuna" });
z.string().startsWith("https://", { message: "Must provide secure URL" });
z.number().lte(5, { message: "this👏is👏too👏big" });
z.infer
를 활용하여 스키마의 추론타입 추출
const User = z.object({
username: z.string(),
});
type User = z.infer<typeof User>; // { username: string }
아래와 같이 number와 string을 모두 허용하나, 출력을 할 때에는 string으로 변환하는 스키마 작성 시.
infer
는 출력 자료형을 기준으로 타입 추론이 된다.
const ID = z
.string()
.or(z.number())
.transform((id) => (typeof id === "number" ? String(id) : id));
type ID = z.infer<typeof ID>;
// ^? type ID = string
입력 자료형을 기준으로 타입을 뽑아내고 싶다면 z.input
을 사용 (output
은 infer
와 동일)
type Input = z.input<typeof ID>;
// ^? type Input = string | number
type Output = z.output<typeof ID>;
// ^? type ID = string
위처럼 input
과 output
타입을 모두 뽑아내면 아래와 같이 유용하게 함수 타이핑이 가능
function processID(input: Input): Output {
const output = ID.parse(input);
// ID 처리 로직
return output;
}
default()
속성에 기본값을 부여.
const User = z.object({
email: z.string(),
active: z.boolean().default(false),
});
const user = User.parse({
email: "user@test.com",
});
console.log(user);
/*
{
email: 'user@test.com',
active: false
}
*/
optional()
모든 스키마를 옵셔널한 값으로 만들 수 있다
const schema = z.optional(z.string());
schema.parse(undefined); // => undefined
type A = z.infer<typeof schema>; // string | undefined
object 내의 요소를 optional한 값으로 설정 가능
const user = z.object({
username: z.string().optional(),
});
type C = z.infer<typeof user>; // { username?: string | undefined };
nullables()
널러블 타입 생성 가능.
const nullableString = z.nullable(z.string());
const nullableString = z.string().nullable(); // 위와 동일
nullableString.parse("asdf"); // => "asdf"
nullableString.parse(null); // => null
nullish()
undefined와 null을 모두 허용
const nullishString = z.string().nullish(); // string | null | undefined
// equivalent to
z.string().nullable().optional();
transform()
입출력간 데이터 변환.
// 입력을 받을 땐 string, number 모두 가능하지만 string으로 전환
const ID = z
.string()
.or(z.number())
.transform((id) => (typeof id === "number" ? String(id) : id));
type ID = z.infer<typeof ID>;
// ^? type ID = string
// 조금 더 복잡한 형태의 transform도 가능
const User = z
.object({
firstName: z.string(),
middleName: z.string().optional(),
lastName: z.string(),
})
.transform((user) => ({
...user,
fullName: user.middleName
? `${user.firstName} ${user.middleName} ${user.lastName}`
: `${user.firstName} ${user.lastName}`,
}));
두 경우 모두 정의한 변환 규칙에 따라서 결과에 fullName
속성이 추가
console.log(User.parse({ firstName: "John", lastName: "Doe" }));
//{ firstName: 'John', lastName: 'Doe', fullName: 'John Doe' }
console.log(User.parse({ firstName: "John", middleName: "K.", lastName: "Doe" }))
//{ firstName: 'John', middleName: 'K.', lastName: 'Doe', fullName: 'John K. Doe }
or()
||
, 또는.
const ID = z.string().or(z.number())
type ID = z.infer<typeof ID>;
// type ID = string | number
and()
&&
, 그리고.
const nameAndAge = z
.object({ name: z.string() })
.and(z.object({ age: z.number() })); // { name: string } & { age: number }
// equivalent to
z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));
refine()
커스텀 validation
const myString = z.string().refine((val) => val.length <= 255, {
message: "String can't be more than 255 characters",
});
refine()
은 두 개의 함수 인자를 받습니다.(value) ⇒ {message: string, path: [string | number], params: object}
```tsx
const longString = z.string().refine(
// 유효성 검사 함수
(val) => val.length > 10,
// 옵션 설정 함수
(val) => ({ message: `${val} is not more than 10 characters` })
);
```
- 옵션 - 에러 경로 지정
```tsx
const passwordForm = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"], // path of error
});
passwordForm.parse({ password: "asdf", confirm: "qwer" });
```
refine()
은 transforms()
와 함께 사용할 수 있다z.string()
.transform((val) => val.length)
.refine((val) => val > 25);
catch()
에러 발생 시 설정 값을 반환
const numberWithCatch = z.number().catch(42);
numberWithCatch.parse(5); // => 5
numberWithCatch.parse("tuna"); // => 42
readonly()
읽기전용
const schema = z.object({ name: string }).readonly();
type schema = z.infer<typeof schema>;
// Readonly<{name: string}>
const result = schema.parse({ name: "fido" });
result.name = "simba"; // error
// 모든 속성은 기본적으로 **required**
const Dog = z.object({
name: z.string(),
age: z.number(),
});
.shape
: 속성의 스키마 타입에 접근Dog.shape.name; // => string schema
Dog.shape.age; // => number schema
keyof()
: 객체의 key값으로 zodEnum 스키마 추출const keySchema = Dog.keyof();
keySchema; // ZodEnum<["name", "age"]>
keyof()
: 객체의 key값으로 zodEnum 스키마 추출const keySchema = Dog.keyof();
keySchema; // ZodEnum<["name", "age"]>
extend()
: 객체 스키마 상속const DogWithBreed = Dog.extend({
breed: z.string(),
});
pick()
: 특정 키만 저장const Dogname = Dog.pick({ name: true });
type Dogname = z.infer<typeof Dogname>; // => { name: string }
omit()
: 특정 키만 삭제const NoNameDog = Dog.omit({ id: true });
type NoNameDog= z.infer<typeof NoNameDog>; // => { age: number }
merge()
: 객체 머지const BaseTeacher = z.object({ students: z.array(z.string()) });
const HasID = z.object({ id: z.string() });
const Teacher = BaseTeacher.merge(HasID);
const Teacher = BeseTeacher.extend(HasId.shape) // merge() 와 동일
type Teacher = z.infer<typeof Teacher>; // => { students: string[], id: string }
partial()
: 일부\전체 속성을 옵셔널한 값으로 지정const user = z.object({
email: z.string(),
username: z.string(),
});
const partialUser = user.partial(); // { email?: string | undefined; username?: string | undefined }
// 옵셔널 속성 지정
const optionalEmail = user.partial({
email: true
}); // { email?: string | undefined; username: string }
//💡.partial은 한 depth만 적용. deepPartial()은 깊이 상관없이 적용가능 //
required()
: 일부/전체 속성을 필수 값으로 지정const partialUser = user.partial();
const requiredUser = partialUser .required(); // { email: string; username: string }
const requiredEmail = partialUser .required({
email: true,
}); // { email: string; username?: string | undefined; }
array를 사용할 땐 호출 순서에 주의해야 한다.
z.string().optional().array(); // (string | undefined)[]
z.string().array().optional(); // string[] | undefined
element
: 배열 요소의 스키마 타에 접근stringArray.element; // => string schema
nonempty
: 배열에 요소가 하나 이상 포함되도록 함const nonEmptyStrings = z.string().array().nonempty();
nonEmptyStrings.parse([]); // throws: "Array cannot be empty"
nonEmptyStrings.parse(["Ariana Grande"]); // passes
// 에러메세지 지정 가능
const nonEmptyStrings = z.string().array().nonempty({
message: "Can't be empty!",
});
min
/ max
/ length
z.string().array().min(5); // 반드시 5개 이상의 항목 포함
z.string().array().max(5); // 반드시 5개 이하의 항목 포함
z.string().array().length(5); // 정확히 5개의 항목 포함
인자와 리턴 타입 정의
const myFunction = z
.function()
.args(z.string(), z.number()) // accepts an arbitrary number of arguments
.returns(z.boolean());
type myFunction = z.infer<typeof myFunction>;
// => (arg0: string, arg1: number)=>boolean
implement()
: 함수의 리턴 개체의 유효성을 검사하여 새로운 함수를 반환const trimmedLength = z
.function()
.args(z.string())
.returns(z.number())
.implement((arg) => {
// TypeScript knows arg is a string!
return arg.trim().length;
});
trimmedLength("sandwich"); // => 8
trimmedLength(" asdf "); // => 4
z.void()
사용 가능z.date()에는 Date
타입만 유효.
zod 3.20ver 부터 z.coerce.date()
로 Date타입 강제 가능.
z.date().safeParse(new Date()); // success: true
z.date().safeParse("2022-01-12T00:00:00.000Z"); // success: false
// v3.2
const dateSchema = z.coerce.date();
dateSchema.safeParse("2023-01-10"); // success: true
dateSchema.safeParse("1/10/23"); // success: true
// 날짜 유형에 맞지 않으면 유효성검사 통과 불가능
dateSchema.safeParse("2023-13-10") // success: false
dateSchema.safeParse("0000-00-00") // success: false