快速入门zod4
2025年8月13日 15:04
1. 简单介绍
- Zod 允许你定义数据模式(Schemas),并基于这些模式进行验证、解析和 TypeScript 类型推断。
2. 基础用法
2.1 安装
npm install zod
或
yarn add zod
2.2 定义数据
import * as z from "zod";
const Player = z.object({
username: z.string(),
xp: z.number()
});
2.3 解析数据( .parse
& .parseAsync
)
Player.parse({ username: "billie", xp: 100 });
// log 输出 => { username: "billie", xp: 100 }
如果您的模式使用某些
异步 API
(如 refinements or transforms), 则需要使用.parseAsync()
await Player.parseAsync({ username: "billie", xp: 100 });
2.4 推断类型 ( z.infer<>
)
const Player = z.object({
username: z.string(),
xp: z.number()
});
// 提取推断的类型
type Player = z.infer<typeof Player>;
// 在你的代码中使用
const player: Player = { username: "billie", xp: 100 };
2.5 处理错误 ( .parse()
& .safeParse()
)
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'
}
] */
}
}
避免阻塞 .safeParse()
const result = Player.safeParse({ username: 42, xp: "100" });
if (!result.success) {
result.error; // ZodError instance
} else {
result.data; // { username: string; xp: number }
}
3. 基础类型
import * as z from "zod";
// primitive types
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();
4. 强制转换 z.coerce
const schema = z.coerce.string();
schema.parse("tuna"); // => "tuna"
schema.parse(42); // => "42"
schema.parse(true); // => "true"
schema.parse(null); // => "null"
5. 字符串 & 字符串格式 & 文字
5.1 字符串
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().regex(/^[a-z]+$/);
z.string().startsWith("aaa");
z.string().endsWith("zzz");
z.string().includes("---");
z.string().uppercase();
z.string().lowercase();
z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toUpperCase
z.string().normalize(); // normalize unicode characters
5.2 字符串格式
z.email()
z.email({ pattern: '您可以自定义正则表达式' })
-
z.uuid()
;
z.uuid({ version: "您可以指定特定的 UUID 版本" });
z.url()
z.url({
hostname: 您可以用正则表达式验证主机名,
protocol: 正则表达式验证协议
})
-
z.hostname()
验证域名或主机名(不包含协议/路径)
z.hostname({
message?: string,
allow_underscores?: boolean, // 是否允许下划线
allow_numeric_tld?: boolean // 是否允许纯数字顶级域名
})
-
z.emoji()
单个 Unicode 表情符号
z.emoji({
message?: string,
allow_multiple?: boolean // 是否允许多个表情(默认 false)
})
-
z.base64()
标准 Base64 编码
z.base64({ padding: false }) // 允许无填充
-
z.base64url()
URL 安全的 Base64 -
z.jwt()
验证 JSON Web 令牌。 -
z.nanoid()
NanoID(默认21字符) -
z.cuid()
CUID v1 -
z.cuid2()
CUID v2 -
z.ulid()
ULID(26字符) -
z.ipv4()
IPv4 地址 -
z.ipv6()
IPv6 地址 -
z.cidrv4()
IPv4 CIDR 块 -
z.cidrv6()
IPv6 CIDR 块 -
z.iso.date()
ISO 8601 日期 (YYYY-MM-DD)(2020-01-23) -
z.iso.time()
ISO 8601 时间 (HH:mm:ss[.SSS][Z])(03:15:00.9999999) -
z.iso.datetime()
ISO 8601 日期时间 -
z.iso.duration()
ISO 8601 持续时间
5.3 文字 z.literal()
const colors = z.literal(["red", "green", "blue"]);
colors.parse("green"); // ✅
colors.parse("yellow"); // ❌
6. 数字 & 整数 & BigInts
6.1 数字验证 z.number()
z.number().gt(5); // 大于
z.number().gte(5); // 大于等于
z.number().lt(5); // 小于
z.number().lte(5); // 小于等于
z.number().positive(); // 必须为正数
z.number().nonnegative(); // 必须为非负数
z.number().negative(); // 必须负数
z.number().nonpositive(); // 必须非正数
z.number().multipleOf(5); // 必须为指定值的倍数
6.2 整数 z.int()
& z.int32()
6.3 BigInts z.bigint()
7. 布尔 & 字符串布尔
7.1 布尔 z.boolean()
7.2 字符串布尔 z.stringbool()
const strbool = z.stringbool();
strbool.parse("true") // => true
strbool.parse("1") // => true
strbool.parse("yes") // => true
strbool.parse("on") // => true
strbool.parse("y") // => true
strbool.parse("enabled") // => true
strbool.parse("false"); // => false
strbool.parse("0"); // => false
strbool.parse("no"); // => false
strbool.parse("off"); // => false
strbool.parse("n"); // => false
strbool.parse("disabled"); // => false
strbool.parse(/* anything else */); // ZodError<[{ code: "invalid_value" }]>
-
z.stringbool()
要自定义真值和假值:
z.stringbool({
truthy: ["true", "1", "yes", "on", "y", "enabled"],
falsy: ["false", "0", "no", "off", "n", "disabled"],
});
8. 日期
z.date()
9. 对象
const UserSchema = z.object({
id: z.number().int(),
name: z.string(),
email: z.string().email(),
isAdmin: z.boolean().optional().default(false)
});
type User = z.infer<typeof UserSchema>;
/* 等价于:
{
id: number;
name: string;
email: string;
isAdmin?: boolean | undefined; // 默认 false
}
*/
9.1 z.strictObject()
- 定义一个严格的模式,该模式在找到未知键时抛出错误
const StrictDog = z.strictObject({
name: z.string(),
});
StrictDog.parse({ name: "Yeller", extraKey: true });
// ❌ throws
9.2 z.looseObject()
- 允许未知键通过的松散模式
const LooseDog = z.looseObject({
name: z.string(),
});
Dog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller", extraKey: true }
9.3 .catchall()
- 要定义将用于验证任何无法识别的键的 catchall 模式
const DogWithStrings = z.object({
name: z.string(),
age: z.number().optional(),
}).catchall(z.string());
DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // ✅
DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // ❌
9.4 .shape()
- 要访问内部模式
const nameSchema = UserSchema.shape.name; // z.string()
9.5 .keyof()
- 要从对象架构的键创建架构
const keySchema = Dog.keyof();
// => ZodEnum<["name", "age"]>
9.6 .extend()
- 要向对象架构添加其他字段
const ExtendedUser = UserSchema.extend({
age: z.number().min(18),
address: z.object({
city: z.string()
})
});
9.7 .pick()
- 选择部分属性
const PublicProfile = UserSchema.pick({ name: true, email: true });
9.8 .omit()
- 排除部分属性
const PrivateUser = UserSchema.omit({ email: true });
9.9 .partial()
- 部分或全部属性可选
const UpdateSchema = UserSchema.partial();
// 所有字段变为可选
const UpdateSchema = UserSchema.partial({ email: true });
// 部分字段变为可选
9.10 .required()
- 部分或全部属性必选
const RequiredUser = UserSchema.required();
// 所有字段变为可选 即使 isAdmin 也必须提供
const RequiredUser = UserSchema.required({ name: true, email: true });
// 部分字段变为可选 即使 isAdmin 也必须提供
10. 可选 & 空值 & 无效 & 未知 & 从不
10.1 可选 z.optional()
const OptionalSchema = z.object({
name: z.string().optional() // 类型: string | undefined
});
10.2 空值 z.nullable()
(可为null)
const schema = z.number().nullable();
type T = z.infer<typeof schema>; // number | null
schema.parse(null); // ✅
schema.parse(42); // ✅
schema.parse(undefined); // ❌
10.3 无效 z.nullish()
(可选和可为 null)
const schema = z.boolean().nullish();
type T = z.infer<typeof schema>; // boolean | null | undefined
schema.parse(null); // ✅
schema.parse(undefined); // ✅
schema.parse(true); // ✅
10.4 未知 z.any()
& z.unknown()
-
any
绕过所有类型检查(不推荐)
const schema = z.any();
schema.parse("text"); // ✅
schema.parse(123); // ✅
schema.parse(new Date()); // ✅
- 安全版的
any
(需要显式验证)
const schema = z.unknown();
// 需要后续验证
schema.parse(input).pipe(
(val) => typeof val === "string" ? val : "default"
);
10.5 从不 z.never()
- 表示永远无效的值
const schema = z.never();
schema.parse("any value"); // ❌ 总是失败
// 实际应用:禁止特定字段
const ForbiddenSchema = z.object({
__internal: z.never().optional() // 禁止此字段存在
});
11. 枚举
11.1 z.enum()
要将模式的值提取为类似枚举的对象
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
FishEnum.parse("Salmon"); // => "Salmon"
FishEnum.parse("Swordfish"); // => ❌
11.2 z.exclude()
要创建新的枚举模式,不包括某些值,请执行以下作:
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);
11.3 z.extract()
要创建新的枚举模式,请提取某些值:
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]);
12. 数组
const stringArray = z.array(z.string()); // or z.string().array()
13. 元组
const MyTuple = z.tuple([
z.string(),
z.number(),
z.boolean()
]);
type MyTuple = z.infer<typeof MyTuple>;
// [string, number, boolean]
14. | 和 &
|
const stringOrNumber = z.union([z.string(), z.number()]);
// string | number
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes
&
const a = z.union([z.number(), z.string()]);
const b = z.union([z.number(), z.boolean()]);
const c = z.intersection(a, b);
type c = z.infer<typeof c>; // => number
15. map
const StringNumberMap = z.map(z.string(), z.number());
type StringNumberMap = z.infer<typeof StringNumberMap>; // Map<string, number>
const myMap: StringNumberMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);
StringNumberMap.parse(myMap);
16. set
const NumberSet = z.set(z.number());
type NumberSet = z.infer<typeof NumberSet>; // Set<number>
const mySet: NumberSet = new Set();
mySet.add(1);
mySet.add(2);
NumberSet.parse(mySet);
17. transform (转换) & pipes (管道)
transform
const castToString = z.transform((val) => String(val));
castToString.parse("asdf"); // => "asdf"
castToString.parse(123); // => "123"
castToString.parse(true); // => "true"
pipes
import { z } from "zod";
// 定义管道:先验证字符串 -> 转数字 -> 验证数字范围
const schema = z.string()
.pipe(z.coerce.number()) // 将字符串转为数字
.pipe(z.number().min(1).max(100)); // 验证数字范围
schema.parse("50"); // ✅ 成功:输出 50 (number)
schema.parse("200"); // ❌ 错误:数字超出范围
18. readonly 只读
const ReadonlyUser = z.object({ name: z.string() }).readonly();
type ReadonlyUser = z.infer<typeof ReadonlyUser>;
// Readonly<{ name: string }>
19. Defaults 默认值
const defaultsTuna = z,string().defaults("tuna")
defaultsTuna.parse(undefined) => "tuna"