zod
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZod Schema Validation
Zod 模式验证
TypeScript-first schema declaration and validation library with static type inference.
一款优先支持 TypeScript 的模式声明与验证库,具备静态类型推断能力。
Why Zod
为什么选择 Zod
- Zero dependencies, 2kb gzipped
- Works in Node.js and browsers
- Immutable API - methods return new instances
- Static type inference - no redundant type declarations
- JSON Schema conversion built-in
- 零依赖,gzip 压缩后仅 2kb
- 支持 Node.js 与浏览器环境
- 不可变 API - 方法返回新实例
- 静态类型推断 - 无需冗余类型声明
- 内置 JSON Schema 转换功能
Requirements
要求
- TypeScript v5.5+
- Enable mode in tsconfig.json
strict
- TypeScript v5.5+
- 在 tsconfig.json 中启用 模式
strict
Core Concepts
核心概念
Schema Definition
模式定义
Always define schemas before validation:
typescript
import { z } from "zod";
// Object schema
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().positive(),
role: z.enum(["admin", "user", "guest"]),
});
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;验证前请先定义模式:
typescript
import { z } from "zod";
// 对象模式
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().positive(),
role: z.enum(["admin", "user", "guest"]),
});
// 从模式中推断 TypeScript 类型
type User = z.infer<typeof UserSchema>;Parsing Methods
解析方法
.parse()ZodErrortypescript
try {
const user = UserSchema.parse(data);
// user is typed as User
} catch (e) {
if (e instanceof z.ZodError) {
console.error(e.issues);
}
}.safeParse()typescript
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // typed User
} else {
console.error(result.error.issues);
}Async variants - Required for async refinements/transforms:
typescript
await UserSchema.parseAsync(data);
await UserSchema.safeParseAsync(data);.parse()ZodErrortypescript
try {
const user = UserSchema.parse(data);
// user 类型为 User
} catch (e) {
if (e instanceof z.ZodError) {
console.error(e.issues);
}
}.safeParse()typescript
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // 类型为 User
} else {
console.error(result.error.issues);
}异步变体 - 异步细化/转换时需要使用:
typescript
await UserSchema.parseAsync(data);
await UserSchema.safeParseAsync(data);Primitive Types
基本类型
typescript
// Basic primitives
z.string()
z.number()
z.bigint()
z.boolean()
z.date()
z.symbol()
z.undefined()
z.null()
z.void()
z.any()
z.unknown()
z.never()
// Coercion - converts input to target type
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)typescript
// 基础类型
z.string()
z.number()
z.bigint()
z.boolean()
z.date()
z.symbol()
z.undefined()
z.null()
z.void()
z.any()
z.unknown()
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)String Validations
字符串验证
typescript
z.string()
.min(1) // Minimum length
.max(255) // Maximum length
.length(10) // Exact length
.email() // Email format
.url() // URL format
.uuid() // UUID format
.cuid() // CUID format
.regex(/pattern/) // Custom regex
.startsWith("prefix")
.endsWith("suffix")
.includes("substring")
.trim() // Transform: trim whitespace
.toLowerCase() // Transform: lowercase
.toUpperCase() // Transform: uppercasetypescript
z.string()
.min(1) // 最小长度
.max(255) // 最大长度
.length(10) // 精确长度
.email() // 邮箱格式
.url() // URL 格式
.uuid() // UUID 格式
.cuid() // CUID 格式
.regex(/pattern/) // 自定义正则
.startsWith("prefix")
.endsWith("suffix")
.includes("substring")
.trim() // 转换:去除首尾空格
.toLowerCase() // 转换:转为小写
.toUpperCase() // 转换:转为大写Number Validations
数字验证
typescript
z.number()
.int() // Integer only
.positive() // > 0
.nonnegative() // >= 0
.negative() // < 0
.nonpositive() // <= 0
.gt(5) // > 5
.gte(5) // >= 5 (alias: .min())
.lt(10) // < 10
.lte(10) // <= 10 (alias: .max())
.multipleOf(5) // Divisible by 5
.finite() // Excludes Infinity
.safe() // Safe integer rangetypescript
z.number()
.int() // 仅允许整数
.positive() // > 0
.nonnegative() // >= 0
.negative() // < 0
.nonpositive() // <= 0
.gt(5) // > 5
.gte(5) // >= 5(别名:.min())
.lt(10) // < 10
.lte(10) // <= 10(别名:.max())
.multipleOf(5) // 可被 5 整除
.finite() // 排除 Infinity
.safe() // 安全整数范围Object Schemas
对象模式
typescript
const PersonSchema = z.object({
name: z.string(),
age: z.number(),
});
// Make all properties optional
PersonSchema.partial();
// Make specific properties optional
PersonSchema.partial({ age: true });
// Make all properties required
PersonSchema.required();
// Pick specific properties
PersonSchema.pick({ name: true });
// Omit specific properties
PersonSchema.omit({ age: true });
// Extend with new properties
PersonSchema.extend({
email: z.string().email(),
});
// Strict mode - reject unknown keys
PersonSchema.strict();
// Passthrough - preserve unknown keys
PersonSchema.passthrough();
// Strip unknown keys (default behavior)
PersonSchema.strip();typescript
const PersonSchema = z.object({
name: z.string(),
age: z.number(),
});
// 将所有属性设为可选
PersonSchema.partial();
// 将指定属性设为可选
PersonSchema.partial({ age: true });
// 将所有属性设为必填
PersonSchema.required();
// 选取指定属性
PersonSchema.pick({ name: true });
// 排除指定属性
PersonSchema.omit({ age: true });
// 扩展新属性
PersonSchema.extend({
email: z.string().email(),
});
// 严格模式 - 拒绝未知键
PersonSchema.strict();
// 透传模式 - 保留未知键
PersonSchema.passthrough();
// 剥离未知键(默认行为)
PersonSchema.strip();Arrays and Tuples
数组与元组
typescript
// Array of strings
z.array(z.string())
.min(1) // At least 1 element
.max(10) // At most 10 elements
.length(5) // Exactly 5 elements
.nonempty(); // At least 1 element (typed)
// Alternative syntax
z.string().array();
// Tuple with fixed positions
z.tuple([
z.string(), // First element: string
z.number(), // Second element: number
]);
// Tuple with rest elements
z.tuple([z.string(), z.number()]).rest(z.boolean());typescript
// 字符串数组
z.array(z.string())
.min(1) // 至少 1 个元素
.max(10) // 最多 10 个元素
.length(5) // 恰好 5 个元素
.nonempty(); // 至少 1 个元素(带类型推断)
// 替代语法
z.string().array();
// 固定位置的元组
z.tuple([
z.string(), // 第一个元素:字符串
z.number(), // 第二个元素:数字
]);
// 带剩余元素的元组
z.tuple([z.string(), z.number()]).rest(z.boolean());Unions and Enums
联合类型与枚举
typescript
// Union types
z.union([z.string(), z.number()]);
// Shorthand
z.string().or(z.number());
// Discriminated unions (better error messages)
z.discriminatedUnion("type", [
z.object({ type: z.literal("email"), email: z.string() }),
z.object({ type: z.literal("phone"), phone: z.string() }),
]);
// Enum from array
z.enum(["admin", "user", "guest"]);
// Native enum
enum Role { Admin, User }
z.nativeEnum(Role);typescript
// 联合类型
z.union([z.string(), z.number()]);
// 简写
z.string().or(z.number());
// 可区分联合类型(更清晰的错误提示)
z.discriminatedUnion("type", [
z.object({ type: z.literal("email"), email: z.string() }),
z.object({ type: z.literal("phone"), phone: z.string() }),
]);
// 从数组创建枚举
z.enum(["admin", "user", "guest"]);
// 原生枚举
enum Role { Admin, User }
z.nativeEnum(Role);Optional and Nullable
可选与可空类型
typescript
// Optional - allows undefined
z.string().optional(); // string | undefined
// Nullable - allows null
z.string().nullable(); // string | null
// Both
z.string().nullish(); // string | null | undefined
// Default values
z.string().default("anonymous");
z.string().optional().default("anonymous");
// Catch - use default on parse failure
z.string().catch("fallback");typescript
// 可选 - 允许 undefined
z.string().optional(); // string | undefined
// 可空 - 允许 null
z.string().nullable(); // string | null
// 同时允许两者
z.string().nullish(); // string | null | undefined
// 默认值
z.string().default("anonymous");
z.string().optional().default("anonymous");
// 捕获失败 - 解析失败时使用默认值
z.string().catch("fallback");Transforms
转换
typescript
// Transform output type
const StringToNumber = z.string().transform((val) => parseInt(val, 10));
type Output = z.output<typeof StringToNumber>; // number
// Chain transforms
z.string()
.trim()
.toLowerCase()
.transform((val) => val.split(","));
// Preprocess input before validation
z.preprocess(
(val) => String(val),
z.string().min(1)
);typescript
// 转换输出类型
const StringToNumber = z.string().transform((val) => parseInt(val, 10));
type Output = z.output<typeof StringToNumber>; // number
// 链式转换
z.string()
.trim()
.toLowerCase()
.transform((val) => val.split(","));
// 验证前预处理输入
z.preprocess(
(val) => String(val),
z.string().min(1)
);Refinements
细化验证
typescript
// Custom validation
z.string().refine(
(val) => val.length <= 255,
{ message: "String must be 255 chars or less" }
);
// Async refinement
z.string().refine(
async (val) => await checkUnique(val),
{ message: "Value must be unique" }
);
// Super refine for complex validations
z.object({
password: z.string(),
confirm: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirm) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Passwords don't match",
path: ["confirm"],
});
}
});typescript
// 自定义验证
z.string().refine(
(val) => val.length <= 255,
{ message: "String must be 255 chars or less" }
);
// 异步细化验证
z.string().refine(
async (val) => await checkUnique(val),
{ message: "Value must be unique" }
);
// Super refine 用于复杂验证
z.object({
password: z.string(),
confirm: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirm) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Passwords don't match",
path: ["confirm"],
});
}
});Error Handling
错误处理
typescript
const result = schema.safeParse(data);
if (!result.success) {
// Access all issues
result.error.issues.forEach((issue) => {
console.log(issue.path); // Field path
console.log(issue.message); // Error message
console.log(issue.code); // Error code
});
// Flatten for form errors
const flat = result.error.flatten();
// { formErrors: string[], fieldErrors: { [key]: string[] } }
// Format for display
const formatted = result.error.format();
// { _errors: string[], field: { _errors: string[] } }
}typescript
const result = schema.safeParse(data);
if (!result.success) {
// 访问所有错误信息
result.error.issues.forEach((issue) => {
console.log(issue.path); // 字段路径
console.log(issue.message); // 错误信息
console.log(issue.code); // 错误代码
});
// 扁平化处理表单错误
const flat = result.error.flatten();
// { formErrors: string[], fieldErrors: { [key]: string[] } }
// 格式化用于展示
const formatted = result.error.format();
// { _errors: string[], field: { _errors: string[] } }
}Common Patterns
常见模式
API Request Validation
API 请求验证
typescript
const CreateUserRequest = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1).max(100),
});
// In Express/Fastify handler
const body = CreateUserRequest.parse(req.body);typescript
const CreateUserRequest = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1).max(100),
});
// 在 Express/Fastify 处理器中
const body = CreateUserRequest.parse(req.body);Environment Variables
环境变量
typescript
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
});
export const env = EnvSchema.parse(process.env);typescript
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
});
export const env = EnvSchema.parse(process.env);Form Data
表单数据
typescript
const ContactForm = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
message: z.string().min(10, "Message must be at least 10 characters"),
});
// With React Hook Form
const { register, handleSubmit } = useForm({
resolver: zodResolver(ContactForm),
});typescript
const ContactForm = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
message: z.string().min(10, "Message must be at least 10 characters"),
});
// 结合 React Hook Form 使用
const { register, handleSubmit } = useForm({
resolver: zodResolver(ContactForm),
});API Response
API 响应
typescript
const ApiResponse = z.object({
data: z.array(UserSchema),
pagination: z.object({
page: z.number(),
total: z.number(),
}),
});
const response = await fetch("/api/users");
const json = await response.json();
const validated = ApiResponse.parse(json);typescript
const ApiResponse = z.object({
data: z.array(UserSchema),
pagination: z.object({
page: z.number(),
total: z.number(),
}),
});
const response = await fetch("/api/users");
const json = await response.json();
const validated = ApiResponse.parse(json);JSON Schema Conversion
JSON Schema 转换
typescript
import { z } from "zod";
// Zod to JSON Schema
const jsonSchema = z.toJSONSchema(UserSchema);
// JSON Schema to Zod
const zodSchema = z.fromJSONSchema(jsonSchema);typescript
import { z } from "zod";
// Zod 转 JSON Schema
const jsonSchema = z.toJSONSchema(UserSchema);
// JSON Schema 转 Zod
const zodSchema = z.fromJSONSchema(jsonSchema);Anti-Patterns
反模式
Avoid redundant type declarations
避免冗余类型声明
typescript
// Bad - duplicates schema
interface User {
name: string;
age: number;
}
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
// Good - infer from schema
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
type User = z.infer<typeof UserSchema>;typescript
// 错误写法 - 重复定义模式
interface User {
name: string;
age: number;
}
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
// 正确写法 - 从模式推断类型
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
type User = z.infer<typeof UserSchema>;Use safeParse for user input
对用户输入使用 safeParse
typescript
// Bad - throws on invalid input
const user = UserSchema.parse(userInput);
// Good - handle errors gracefully
const result = UserSchema.safeParse(userInput);
if (!result.success) {
return { errors: result.error.flatten().fieldErrors };
}typescript
// 错误写法 - 输入无效时抛出异常
const user = UserSchema.parse(userInput);
// 正确写法 - 优雅处理错误
const result = UserSchema.safeParse(userInput);
if (!result.success) {
return { errors: result.error.flatten().fieldErrors };
}Prefer discriminated unions
优先使用可区分联合类型
typescript
// Bad - ambiguous errors
z.union([
z.object({ email: z.string() }),
z.object({ phone: z.string() }),
]);
// Good - clear error messages
z.discriminatedUnion("contactType", [
z.object({ contactType: z.literal("email"), email: z.string() }),
z.object({ contactType: z.literal("phone"), phone: z.string() }),
]);typescript
// 错误写法 - 错误提示模糊
z.union([
z.object({ email: z.string() }),
z.object({ phone: z.string() }),
]);
// 正确写法 - 清晰的错误提示
z.discriminatedUnion("contactType", [
z.object({ contactType: z.literal("email"), email: z.string() }),
z.object({ contactType: z.literal("phone"), phone: z.string() }),
]);