이 글은 Chat GPT로 TypeScript를 공부하며 정리한 글이다.
Form을 만들 때 다음 두 가지를 항상 반복한다.
하지만 타입스크립트를 쓰는 이상,
이미 Form 타입이 있는데 왜 또 검증 스키마를 수동으로 만들어야 할까?
이번 글에서는 Form 타입에서 Validation 규칙을 자동으로 유도하는 시스템을 만든다.
즉, “타입 = 데이터 구조 + 제약 조건”
으로 확장하는 패턴을 다룬다.
이전 글의 FormMapper에서 이미 이런 타입을 얻을 수 있었다.
type UserForm = {
'user.profile.name': string;
'user.profile.email': string;
'user.profile.age': number;
};
이제 이 타입을 기반으로 자동 검증 스키마를 생성한다.
타입 기반 Validation 매퍼 설계
type ValidationRule<T> =
T extends string
? { required: boolean; maxLength?: number; pattern?: RegExp }
: T extends number
? { required: boolean; min?: number; max?: number }
: T extends boolean
? { required: boolean }
: never;
이제 각 필드 타입을 돌며 규칙을 자동으로 만든다.
type ValidationMap<T> = {
[K in keyof T]: ValidationRule<T[K]>;
};
type UserValidation = ValidationMap<UserForm>;
/*
{
"user.profile.name": { required: boolean; maxLength?: number; pattern?: RegExp };
"user.profile.email": { required: boolean; maxLength?: number; pattern?: RegExp };
"user.profile.age": { required: boolean; min?: number; max?: number };
}
*/
✅ 결과적으로, 각 필드에 자동으로 규칙 슬롯이 생긴다.
런타임에서도 활용할 수 있도록 createValidator를 만든다.
function createValidator<T extends Record<string, any>>(
rules: ValidationMap<T>
) {
return (values: T) => {
const errors: Partial<Record<keyof T, string>> = {};
for (const key in values) {
const value = values[key];
const rule = rules[key];
if (rule.required && (value === null || value === undefined || value === "")) {
errors[key] = "필수 항목입니다.";
continue;
}
if (typeof value === "string") {
if (rule.maxLength && value.length > rule.maxLength)
errors[key] = `최대 ${rule.maxLength}자 이하로 입력하세요.`;
if (rule.pattern && !rule.pattern.test(value))
errors[key] = `형식이 올바르지 않습니다.`;
}
if (typeof value === "number") {
if (rule.min !== undefined && value < rule.min)
errors[key] = `${rule.min} 이상이어야 합니다.`;
if (rule.max !== undefined && value > rule.max)
errors[key] = `${rule.max} 이하이어야 합니다.`;
}
}
return errors;
};
}
const rules: ValidationMap<UserForm> = {
"user.profile.name": { required: true, maxLength: 10 },
"user.profile.email": { required: true, pattern: /^[^@]+@[^@]+\.[^@]+$/ },
"user.profile.age": { required: true, min: 1, max: 120 },
};
const validate = createValidator(rules);
const result = validate({
"user.profile.name": "홍길동",
"user.profile.email": "test@example.com",
"user.profile.age": 25,
});
console.log(result); // ✅ {}
✅ rules의 타입이 UserForm 기반으로 자동 검증되어
필드 이름이나 타입이 틀리면 컴파일 타임에 에러가 발생한다.
(예: "user.profile.ag" → ❌)
| 기능 | 설명 |
|---|---|
zod 스키마 자동 변환 | ValidationMap<T>을 바탕으로 z.object() 자동 생성 |
| 서버 DTO와 동기화 | 백엔드 응답 모델과 동일한 타입에서 검증 규칙 파생 |
| FormBuilder 통합 | 폼 UI, 검증, 제출 로직까지 타입 기반 자동화 |
| 다국어 메시지 매핑 | keyof T를 기반으로 i18n 키 자동 생성 |
| 개념 | 설명 |
|---|---|
| ValidationRule | 타입에 따라 다른 검증 규칙 생성 |
| ValidationMap | 모든 필드의 검증 규칙 집합 |
| createValidator | 런타임 검증 로직 생성 |
| 효과 | 타입과 검증 스키마의 완전한 동기화 |
💡 한 문장 요약
“타입이 곧 검증 규칙이다.”
— Form 구조, Validation, Submission 모두 타입에서 출발한다.