zod 模式验证库
Zod是一个TypeScript优先的模式验证库,它能够以简洁的链式API定义数据模式并对数据进行校验。Zod的设计目标是消除重复的类型声明,开发者只需定义一次验证器,Zod就能自动推断出对应的TypeScript类型。在实际项目中,Zod常用于表单验证、API请求和响应校验、环境变量验证等场景,Zod是目前NodeJS生态中最流行的模式验证库之一。
项目主页:https://zod.dev/
安装
执行以下命令使用npm安装Zod。
npm i zod
Zod是零依赖的轻量级库,其原生支持TypeScript,无需额外安装类型定义包。
基础用法
定义模式并验证数据
Zod的核心用法是定义模式(Schema)并用它来验证数据,下面是一个简单的例子。
import { z } from "zod";
// 定义一个字符串模式
const nameSchema = z.string();
// 验证数据
const result1 = nameSchema.parse("Tom"); // 返回`Tom`
const result2 = nameSchema.parse(123); // 抛出`ZodError`异常
parse()方法用于验证数据,在验证成功时,它会返回根据模式自动推断的类型,在验证失败时则会抛出异常,如果希望不抛出异常而是返回验证结果对象,可以使用safeParse()方法。
const result = nameSchema.safeParse(123);
if (result.success) {
console.log(result.data); // 验证成功,获取数据
} else {
console.log(result.error); // 验证失败,获取错误信息
}
基础类型模式
Zod提供了丰富的基础类型模式。
import { z } from "zod";
// 基础类型
z.string(); // 字符串
z.number(); // 数字
z.boolean(); // 布尔值
z.bigint(); // BigInt
z.date(); // Date对象
z.undefined(); // undefined
z.null(); // null
z.void(); // void
z.any(); // any类型
z.unknown(); // unknown类型
// 字面量类型
z.literal("hello"); // 只能是 "hello"
z.literal(42); // 只能是 42
对象模式
除了基础类型模式,实际开发中最常用的是对象模式,它用于验证复杂的数据结构是否符合要求。
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
});
const user = userSchema.parse({
name: "Tom",
age: 18,
email: "tom@example.com",
});
这里parse()返回的user是一个自动推断的包含用户模式规定字段的类型,我们可以从其中取用name、age等字段的值。
对象模式提供了一些实用的方法来扩展或修改模式。
// 所有字段可选
const partialUserSchema = userSchema.partial();
// 只保留部分字段
const nameOnlySchema = userSchema.pick({ name: true });
// 排除部分字段
const withoutEmailSchema = userSchema.omit({ email: true });
// 扩展字段
const extendedUserSchema = userSchema.extend({
role: z.string(),
});
// 合并两个对象模式
const mergedSchema = userSchema.merge(anotherSchema);
数组模式
数组模式用于验证数组类型的数据。
import { z } from "zod";
// 字符串数组
const stringArraySchema = z.array(z.string());
stringArraySchema.parse(["a", "b", "c"]); // 通过
stringArraySchema.parse(["a", 1, "c"]); // 失败
// 非空数组
const nonEmptyArraySchema = z.array(z.number()).nonempty();
// 限制数组长度
const limitedArraySchema = z.array(z.string()).min(1).max(10);
联合类型和可选类型
import { z } from "zod";
// 联合类型
const stringOrNumberSchema = z.union([z.string(), z.number()]);
// 简写形式
const stringOrNumberSchema2 = z.string().or(z.number());
// 可选类型
const optionalStringSchema = z.string().optional(); // string | undefined
// 可空类型
const nullableStringSchema = z.string().nullable(); // string | null
// 带默认值
const defaultStringSchema = z.string().default("default value");
枚举类型
import { z } from "zod";
// Zod枚举
const roleSchema = z.enum(["admin", "user", "guest"]);
type Role = z.infer<typeof roleSchema>; // "admin" | "user" | "guest"
// 使用TypeScript原生枚举
enum Status {
Active = "active",
Inactive = "inactive",
}
const statusSchema = z.nativeEnum(Status);
自动推断TypeScript类型
Zod最强大的特性之一是能够从模式中自动推断TypeScript类型,避免重复声明。
import { z } from "zod";
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
age: z.number().optional(),
});
// 自动推断类型
type User = z.infer<typeof userSchema>;
// 等同于以下手动声明:
// type User = {
// id: number;
// name: string;
// email: string;
// age?: number;
// }
// 使用推断的类型
const createUser = (data: User) => {
// ...
};
字符串验证方法
Zod为字符串类型提供了丰富的验证方法,下面是一些例子。
import { z } from "zod";
z.string().min(5); // 最小长度
z.string().max(100); // 最大长度
z.string().length(10); // 精确长度
z.string().email(); // 邮箱格式
z.string().url(); // URL格式
z.string().uuid(); // UUID格式
z.string().regex(/^[a-z]+$/); // 正则匹配
z.string().startsWith("https://"); // 前缀匹配
z.string().endsWith(".com"); // 后缀匹配
z.string().trim(); // 自动去除首尾空格
z.string().toLowerCase(); // 自动转小写
z.string().toUpperCase(); // 自动转大写
数字验证方法
对于数字Zod也提供了很多验证方法。
import { z } from "zod";
z.number().min(0); // 最小值
z.number().max(100); // 最大值
z.number().int(); // 整数
z.number().positive(); // 正数
z.number().negative(); // 负数
z.number().nonnegative(); // 非负数
z.number().nonpositive(); // 非正数
z.number().finite(); // 有限数
z.number().multipleOf(5); // 5的倍数
自定义验证
当内置验证方法无法满足需求时,可以使用refine()方法添加自定义验证逻辑。
import { z } from "zod";
// 简单自定义验证
const passwordSchema = z.string().refine(
(val) => val.length >= 8 && /[A-Z]/.test(val) && /[0-9]/.test(val),
{ message: "密码必须至少8位且包含大写字母和数字" }
);
// 跨字段验证
const formSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine(
(data) => data.password === data.confirmPassword,
{
message: "两次输入的密码不一致",
path: ["confirmPassword"], // 指定错误路径
}
);
对于需要进行异步验证的场景(如检查用户名是否已存在),可以使用refineAsync()方法。
数据转换
Zod支持在验证的同时对数据进行转换,下面是一些例子。
import { z } from "zod";
// 使用transform转换数据
const numberStringSchema = z.string().transform((val) => parseInt(val, 10));
const result = numberStringSchema.parse("42"); // 返回数字类型的`42`
// 使用coerce进行类型强制转换
const coercedNumberSchema = z.coerce.number();
coercedNumberSchema.parse("42"); // 返回`42`
coercedNumberSchema.parse(true); // 返回`1`
const coercedDateSchema = z.coerce.date();
coercedDateSchema.parse("2024-01-01"); // 返回`Date`对象
错误处理
Zod提供了详细的错误信息,便于定位验证失败的原因。
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(2, "名称至少2个字符"),
age: z.number().min(0, "年龄不能为负数"),
});
try {
userSchema.parse({ name: "T", age: -1 });
} catch (error) {
if (error instanceof z.ZodError) {
console.log(error.issues);
// [
// { code: 'too_small', message: '名称至少2个字符', path: ['name'], ... },
// { code: 'too_small', message: '年龄不能为负数', path: ['age'], ... }
// ]
// 格式化错误信息
console.log(error.flatten());
// { fieldErrors: { name: ['名称至少2个字符'], age: ['年龄不能为负数'] } }
}
}
实际应用场景案例
API响应验证
在调用外部API时,使用Zod验证响应数据可以确保类型安全。
import { z } from "zod";
const apiResponseSchema = z.object({
code: z.number(),
data: z.object({
users: z.array(z.object({
id: z.number(),
name: z.string(),
})),
}),
});
const fetchUsers = async () => {
const response = await fetch("/api/users");
const json = await response.json();
return apiResponseSchema.parse(json); // 验证并返回类型安全的数据
};
环境变量验证
使用Zod验证环境变量可以在应用启动时尽早发现配置问题。
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
PORT: z.coerce.number().default(3000),
});
export const env = envSchema.parse(process.env);