普通视图

发现新文章,点击刷新页面。
昨天以前首页

快速入门zod4

作者 哈撒Ki
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"
❌
❌