💎 Zod 4 is now stable!  Read the announcement.
Zod logo

定义 Schema

要验证数据,你必须首先定义一个 Schema(模式)。Schema 代表 类型,从简单的原始值到复杂的嵌套对象和数组。

原始类型 (Primitives)

import * as z from "zod";
 
// primitive types
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();

强制类型转换 (Coercion)

要将输入数据强制转换为适当的类型,请改用 z.coerce

z.coerce.string();    // String(input)
z.coerce.number();    // Number(input)
z.coerce.boolean();   // Boolean(input)
z.coerce.bigint();    // BigInt(input)

这些 Schema 的强制转换变体尝试将输入值转换为适当的类型。

const schema = z.coerce.string();
 
schema.parse("tuna");    // => "tuna"
schema.parse(42);        // => "42"
schema.parse(true);      // => "true"
schema.parse(null);      // => "null"

默认情况下,这些强制转换 schema 的输入类型是 unknown。要指定更具体的输入类型,请传递一个泛型参数:

const A = z.coerce.number();
type AInput = z.input<typeof A>; // => unknown
 
const B = z.coerce.number<number>();
type BInput = z.input<typeof B>; // => number

字面量 (Literals)

字面量 schema 代表一个 字面量类型,例如 "hello world"5

const tuna = z.literal("tuna");
const twelve = z.literal(12);
const twobig = z.literal(2n);
const tru = z.literal(true);

要表示 JavaScript 字面量 nullundefined

z.null();
z.undefined();
z.void(); // 等同于 z.undefined()

允许及个字面量值:

const colors = z.literal(["red", "green", "blue"]);
 
colors.parse("green"); // ✅
colors.parse("yellow"); // ❌

从字面量 schema 中提取允许值的集合:

colors.values; // => Set<"red" | "green" | "blue">

字符串 (Strings)

Zod 提供了少量内置的字符串验证和转换 API。执行一些常见的字符串验证:

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(); // 去除空白
z.string().toLowerCase(); // 转小写
z.string().toUpperCase(); // 转大写
z.string().normalize(); // 标准化 unicode 字符

字符串格式 (String formats)

要针对一些常见的字符串格式进行验证:

z.email();
z.uuid();
z.url();
z.httpUrl();       // 仅限 http 或 https URL
z.hostname();
z.emoji();         // 验证单个 emoji 字符
z.base64();
z.base64url();
z.hex();
z.jwt();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.mac();
z.cidrv4();        // ipv4 CIDR 块
z.cidrv6();        // ipv6 CIDR 块
z.hash("sha256");  // 或 "sha1", "sha384", "sha512", "md5"
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();

电子邮件 (Emails)

要验证电子邮件地址:

z.email();

默认情况下,Zod 使用相对严格的电子邮件正则表达式,旨在验证包含常用字符的普通电子邮件地址。它大致相当于 Gmail 强制执行的规则。要了解有关此正则表达式的更多信息,请参阅 这篇文章

/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_+-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/i

要自定义电子邮件验证行为,可以将自定义正则表达式传递给 pattern 参数。

z.email({ pattern: /your regex here/ });

Zod 导出了几个你可以使用的有用正则表达式。

// Zod 的默认电子邮件正则表达式
z.email();
z.email({ pattern: z.regexes.email }); // 等价
 
// 浏览器用于验证 input[type=email] 字段的正则表达式
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });
 
// 经典的 emailregex.com 正则表达式 (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });
 
// 允许 Unicode 的宽松正则表达式(适用于国际电子邮件)
z.email({ pattern: z.regexes.unicodeEmail });

UUID

要验证 UUID:

z.uuid();

要指定特定的 UUID 版本:

// 支持 "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"
z.uuid({ version: "v4" });
 
// 为方便起见
z.uuidv4();
z.uuidv6();
z.uuidv7();

RFC 9562/4122 UUID 规范要求第 8 个字节的前两位为 10。其他类似 UUID 的标识符不强制执行此约束。要验证任何类似 UUID 的标识符:

z.guid();

URL

要验证任何兼容 WHATWG 的 URL:

const schema = z.url();
 
schema.parse("https://example.com"); // ✅
schema.parse("http://localhost"); // ✅
schema.parse("mailto:[email protected]"); // ✅

如你所见,这是非常宽松的。在内部,它使用 new URL() 构造函数来验证输入;此行为可能因平台和运行时而异,但它是验证任何给定 JS 运行时/引擎上的 URI/URL 的最严格的方法。

要针对特定正则表达式验证主机名:

const schema = z.url({ hostname: /^example\.com$/ });
 
schema.parse("https://example.com"); // ✅
schema.parse("https://zombo.com"); // ❌

要针对特定正则表达式验证协议,请使用 protocol 参数。

const schema = z.url({ protocol: /^https$/ });
 
schema.parse("https://example.com"); // ✅
schema.parse("http://example.com"); // ❌

Web URL — 在许多情况下,你会希望专门验证 Web URL。这是推荐的 schema:

const httpUrl = z.url({
  protocol: /^https?$/,
  hostname: z.regexes.domain
});

这限制协议为 http/https,并确保主机名是带有 z.regexes.domain 正则表达式的有效域名:

/^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/

To normalize URLs, use the normalize flag. This will overwrite the input value with the normalized URL returned by new URL().

new URL("HTTP://ExAmPle.com:80/./a/../b?X=1#f oo").href
// => "http://example.com/b?X=1#f%20oo"

ISO 日期时间 (ISO datetimes)

你可能已经注意到,Zod string 包含一些日期/时间相关的验证。这些验证是基于正则表达式的,因此不如完整的日期/时间库严格。但是,它们对于验证用户输入非常方便。

z.iso.datetime() 方法强制执行 ISO 8601;默认情况下,不允许时区偏移:

const datetime = z.iso.datetime();
 
datetime.parse("2020-01-01T06:15:00Z"); // ✅
datetime.parse("2020-01-01T06:15:00.123Z"); // ✅
datetime.parse("2020-01-01T06:15:00.123456Z"); // ✅ (任意精度)
datetime.parse("2020-01-01T06:15:00+02:00"); // ❌ (不允许偏移)
datetime.parse("2020-01-01T06:15:00"); // ❌ (不允许本地时间)

允许时区偏移:

const datetime = z.iso.datetime({ offset: true });
 
// 允许时区偏移
datetime.parse("2020-01-01T06:15:00+02:00"); // ✅
 
// 不允许基本偏移
datetime.parse("2020-01-01T06:15:00+02");    // ❌
datetime.parse("2020-01-01T06:15:00+0200");  // ❌
 
// 仍然支持 Z
datetime.parse("2020-01-01T06:15:00Z"); // ✅ 

允许非限定(无时区)的日期时间:

const schema = z.iso.datetime({ local: true });
schema.parse("2020-01-01T06:15:01"); // ✅
schema.parse("2020-01-01T06:15"); // ✅ 秒也是可选的

限制允许的时间 precision(精度)。默认情况下,秒是可选的,并且允许任意的亚秒精度。

const a = z.iso.datetime();
a.parse("2020-01-01T06:15Z"); // ✅
a.parse("2020-01-01T06:15:00Z"); // ✅
a.parse("2020-01-01T06:15:00.123Z"); // ✅
 
const b = z.iso.datetime({ precision: -1 }); // 分钟精度(无秒)
b.parse("2020-01-01T06:15Z"); // ✅
b.parse("2020-01-01T06:15:00Z"); // ❌
b.parse("2020-01-01T06:15:00.123Z"); // ❌
 
const c = z.iso.datetime({ precision: 0 }); // 仅秒精度
c.parse("2020-01-01T06:15Z"); // ❌
c.parse("2020-01-01T06:15:00Z"); // ✅
c.parse("2020-01-01T06:15:00.123Z"); // ❌
 
const d = z.iso.datetime({ precision: 3 }); // 仅毫秒精度
d.parse("2020-01-01T06:15Z"); // ❌
d.parse("2020-01-01T06:15:00Z"); // ❌
d.parse("2020-01-01T06:15:00.123Z"); // ✅

ISO 日期 (ISO dates)

z.iso.date() 方法验证格式为 YYYY-MM-DD 的字符串。

const date = z.iso.date();
 
date.parse("2020-01-01"); // ✅
date.parse("2020-1-1"); // ❌
date.parse("2020-01-32"); // ❌

ISO 时间 (ISO times)

z.iso.time() 方法验证格式为 HH:MM[:SS[.s+]] 的字符串。默认情况下,秒是可选的,亚秒小数也是如此。

const time = z.iso.time();
 
time.parse("03:15"); // ✅
time.parse("03:15:00"); // ✅
time.parse("03:15:00.9999999"); // ✅ (arbitrary precision)

不允许任何形式的偏移。

time.parse("03:15:00Z"); // ❌ (no `Z` allowed)
time.parse("03:15:00+02:00"); // ❌ (no offsets allowed)

使用 precision 参数来限制允许的小数精度。

z.iso.time({ precision: -1 }); // HH:MM (minute precision)
z.iso.time({ precision: 0 });  // HH:MM:SS (second precision)
z.iso.time({ precision: 1 });  // HH:MM:SS.s (decisecond precision)
z.iso.time({ precision: 2 });  // HH:MM:SS.ss (centisecond precision)
z.iso.time({ precision: 3 });  // HH:MM:SS.sss (millisecond precision)

IP 地址 (IP addresses)

const ipv4 = z.ipv4();
ipv4.parse("192.168.0.0"); // ✅
 
const ipv6 = z.ipv6();
ipv6.parse("2001:db8:85a3::8a2e:370:7334"); // ✅

IP 块 (CIDR)

验证使用 CIDR 表示法 指定的 IP 地址范围。

const cidrv4 = z.cidrv4();
cidrv4.parse("192.168.0.0/24"); // ✅
 
const cidrv6 = z.cidrv6();
cidrv6.parse("2001:db8::/32"); // ✅

MAC 地址 (MAC Addresses)

验证标准 48 位 MAC 地址 IEEE 802

const mac = z.mac(); 
mac.parse("00:1A:2B:3C:4D:5E");  // ✅
mac.parse("00-1a-2b-3c-4d-5e");  // ❌ 默认冒号分隔
mac.parse("001A:2B3C:4D5E");     // ❌ 仅限标准格式
mac.parse("00:1A:2b:3C:4d:5E");  // ❌ 禁止大小写混合
 
// 自定义分隔符
const dashMac = z.mac({ delimiter: "-" });
dashMac.parse("00-1A-2B-3C-4D-5E"); // ✅

JWT

验证 JSON Web Tokens

z.jwt();
z.jwt({ alg: "HS256" });

哈希 (Hashes)

要验证加密哈希值:

z.hash("md5");
z.hash("sha1");
z.hash("sha256");
z.hash("sha384");
z.hash("sha512");

默认情况下,z.hash() 期望十六进制编码,这是惯例。你可以使用 enc 参数指定不同的编码:

z.hash("sha256", { enc: "hex" });       // 默认
z.hash("sha256", { enc: "base64" });    // base64 编码
z.hash("sha256", { enc: "base64url" }); // base64url 编码 (无填充)

自定义格式 (Custom formats)

要定义你自己的字符串格式:

const coolId = z.stringFormat("cool-id", ()=>{
  // arbitrary validation here
  return val.length === 100 && val.startsWith("cool-");
});
 
// a regex is also accepted
z.stringFormat("cool-id", /^cool-[a-z0-9]{95}$/);

此 schema 将产生 "invalid_format" 问题,这比 refinements 或 z.custom() 产生的 "custom" 错误更具描述性。

myFormat.parse("invalid input!");
// ZodError: [
//   {
//     "code": "invalid_format",
//     "format": "cool-id",
//     "path": [],
//     "message": "Invalid cool-id"
//   }
// ]

模板字面量 (Template literals)

新功能 — 在 [email protected] 中引入。

要定义模板字面量 schema:

const schema = z.templateLiteral([ "hello, ", z.string(), "!" ]);
// `hello, ${string}!`

z.templateLiteral API 可以处理任意数量的字符串字面量(例如 "hello")和 schema。任何推断类型可分配给 string | number | bigint | boolean | null | undefined 的 schema 都可以传递。

z.templateLiteral([ "hi there" ]);
// `hi there`
 
z.templateLiteral([ "email: ", z.string() ]);
// `email: ${string}`
 
z.templateLiteral([ "high", z.literal(5) ]);
// `high5`
 
z.templateLiteral([ z.nullable(z.literal("grassy")) ]);
// `grassy` | `null`
 
z.templateLiteral([ z.number(), z.enum(["px", "em", "rem"]) ]);
// `${number}px` | `${number}em` | `${number}rem`

数字 (Numbers)

使用 z.number() 验证数字。它允许任何有限数字。

const schema = z.number();
 
schema.parse(3.14);      // ✅
schema.parse(NaN);       // ❌
schema.parse(Infinity);  // ❌

Zod 实现了几个针对数字的验证:

z.number().gt(5);
z.number().gte(5);                     // alias .min(5)
z.number().lt(5);
z.number().lte(5);                     // alias .max(5)
z.number().positive();       
z.number().nonnegative();    
z.number().negative(); 
z.number().nonpositive(); 
z.number().multipleOf(5);              // alias .step(5)

如果(出于某种原因)你想验证 NaN,请使用 z.nan()

z.nan().parse(NaN);              // ✅
z.nan().parse("anything else");  // ❌

整数 (Integers)

要验证整数:

z.int();     // 限制为安全整数范围
z.int32();   // 限制为 int32 范围

BigInts

要验证 BigInts:

z.bigint();

Zod 包含一些针对 bigint 的验证。

z.bigint().gt(5n);
z.bigint().gte(5n);                    // alias `.min(5n)`
z.bigint().lt(5n);
z.bigint().lte(5n);                    // alias `.max(5n)`
z.bigint().positive(); 
z.bigint().nonnegative(); 
z.bigint().negative(); 
z.bigint().nonpositive(); 
z.bigint().multipleOf(5n);             // alias `.step(5n)`

布尔值 (Booleans)

要验证布尔值:

z.boolean().parse(true); // => true
z.boolean().parse(false); // => false

日期 (Dates)

使用 z.date() 验证 Date 实例。

z.date().safeParse(new Date()); // success: true
z.date().safeParse("2022-01-12T06:15:00.000Z"); // success: false

要自定义错误消息:

z.date({
  error: issue => issue.input === undefined ? "Required" : "Invalid date"
});

Zod 提供了少量针对日期的验证。

z.date().min(new Date("1900-01-01"), { error: "Too old!" });
z.date().max(new Date(), { error: "Too young!" });

枚举 (Enums)

使用 z.enum 根据一组固定的允许 字符串 值来验证输入。

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
 
FishEnum.parse("Salmon"); // => "Salmon"
FishEnum.parse("Swordfish"); // => ❌

小心 — 如果你将字符串数组声明为变量,Zod 将无法正确推断每个元素的具体值。

const fish = ["Salmon", "Tuna", "Trout"];
 
const FishEnum = z.enum(fish);
type FishEnum = z.infer<typeof FishEnum>; // string

要解决此问题,请始终将数组直接传递给 z.enum() 函数,或使用 as const

const fish = ["Salmon", "Tuna", "Trout"] as const;
 
const FishEnum = z.enum(fish);
type FishEnum = z.infer<typeof FishEnum>; // "Salmon" | "Tuna" | "Trout"

支持类枚举的对象字面量 ({ [key: string]: string | number })。

const Fish = {
  Salmon: 0,
  Tuna: 1
} as const
 
const FishEnum = z.enum(Fish)
FishEnum.parse(Fish.Salmon); // => ✅
FishEnum.parse(0); // => ✅
FishEnum.parse(2); // => ❌

你也可以传入外部声明的 TypeScript 枚举。

enum Fish {
  Salmon = 0,
  Tuna = 1
}
 
const FishEnum = z.enum(Fish);
FishEnum.parse(Fish.Salmon); // => ✅
FishEnum.parse(0); // => ✅
FishEnum.parse(2); // => ❌

Zod 4 — 这替代了 Zod 3 中的 z.nativeEnum() API。

注意:不推荐 使用 TypeScript 的 enum 关键字。

enum Fish {
  Salmon = "Salmon",
  Tuna = "Tuna",
  Trout = "Trout",
}
 
const FishEnum = z.enum(Fish);

.enum

要将 schema 的值提取为类枚举对象:

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
 
FishEnum.enum;
// => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" }

.exclude()

创建一个新的枚举 schema,排除某些值:

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);

.extract()

创建一个新的枚举 schema,提取某些值:

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]);

Stringbools

💎 Zod 4 新功能

在某些情况下(例如解析环境变量),将某些字符串 "boolish"(类似布尔值)值解析为纯 boolean 值很有价值。为了支持这一点,Zod 4 引入了 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" }]>

要自定义 truthy 和 falsy 值:

// 这些是默认值
z.stringbool({
  truthy: ["true", "1", "yes", "on", "y", "enabled"],
  falsy: ["false", "0", "no", "off", "n", "disabled"],
});

默认情况下,该 schema 是 不区分大小写 的;所有输入在与 truthy/falsy 值比较之前都会转换为小写。要使其区分大小写:

z.stringbool({
  case: "sensitive"
});

可选类型 (Optionals)

使 schema 变为 可选(即允许 undefined 输入)。

z.optional(z.literal("yoda")); // 或者 z.literal("yoda").optional()

这将返回一个包装原始 schema 的 ZodOptional 实例。要提取内部 schema:

optionalYoda.unwrap(); // ZodLiteral<"yoda">

可空类型 (Nullables)

使 schema 变为 可空(即允许 null 输入)。

z.nullable(z.literal("yoda")); // 或者 z.literal("yoda").nullable()

这将返回一个包装原始 schema 的 ZodNullable 实例。要提取内部 schema:

nullableYoda.unwrap(); // ZodLiteral<"yoda">

Nullish

使 schema 变为 nullish(既可选又可空):

const nullishYoda = z.nullish(z.literal("yoda"));

请参阅 TypeScript 手册以了解有关的概念 nullish

Unknown

Zod 旨在与 TypeScript 的类型系统一一对应。因此,Zod 提供了 API 来表示以下特殊类型:

// 允许任何值
z.any(); // 推断类型:`any`
z.unknown(); // 推断类型:`unknown`

Never

没有值可以通过验证。

z.never(); // 推断类型:`never`

对象 (Objects)

要定义对象类型:

  // 默认情况下所有属性都是必需的
  const Person = z.object({
    name: z.string(),
    age: z.number(),
  });
 
  type Person = z.infer<typeof Person>;
  // => { name: string; age: number; }

默认情况下,所有属性都是必需的。要使某些属性可选:

const Dog = z.object({
  name: z.string(),
  age: z.number().optional(),
});
 
Dog.parse({ name: "Yeller" }); // ✅

默认情况下,无法识别的键会从解析结果中 剥离

Dog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller" }

z.strictObject

定义 严格 schema,当发现未知键时抛出错误:

const StrictDog = z.strictObject({
  name: z.string(),
});
 
StrictDog.parse({ name: "Yeller", extraKey: true });
// ❌ 抛出错误

z.looseObject

定义 宽松 schema,允许未知键通过:

const LooseDog = z.looseObject({
  name: z.string(),
});
 
LooseDog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller", extraKey: true }

.catchall()

定义 catchall schema,用于验证所有无法识别的键:

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 }); // ❌

.shape

访问内部 schema:

Dog.shape.name; // => string schema
Dog.shape.age; // => number schema

.keyof()

从对象 schema 的键创建 ZodEnum schema:

const keySchema = Dog.keyof();
// => ZodEnum<["name", "age"]>

.extend()

向对象 schema 添加字段:

const DogWithBreed = Dog.extend({
  breed: z.string(),
});

此 API 可用于覆盖现有字段!小心使用这种能力!如果两个 schema 共享键,B 将覆盖 A。

替代方案:展开语法 — 你也可以完全避免使用 .extend(),而是创建一个全新的对象 schema。这使得结果 schema 的严格级别在视觉上更加明显。

const DogWithBreed = z.object({ // or z.strictObject() or z.looseObject()...
  ...Dog.shape,
  breed: z.string(),
});

你也可以使用它一次合并多个对象。

const DogWithBreed = z.object({
  ...Animal.shape,
  ...Pet.shape,
  breed: z.string(),
});

这种方法有几个优点:

  1. 它使用语言级功能(展开语法)而不是特定于库的 API
  2. 相同的语法在 Zod 和 Zod Mini 中都有效
  3. tsc 效率更高 — .extend() 方法在大型 schema 上可能很昂贵,并且由于 TypeScript 限制 当调用被链接时它会变得更加昂贵
  4. 如果你愿意,你可以通过使用 z.strictObject()z.looseObject() 更改结果 schema 的严格级别

.safeExtend()

.safeExtend() 方法的工作方式类似于 .extend(),但它不允许你用不可分配的 schema 覆盖现有属性。换句话说,.safeExtend() 的结果将具有 extends 原始类型(在 TypeScript 意义上)的推断类型。

z.object({ a: z.string() }).safeExtend({ a: z.string().min(5) }); // ✅
z.object({ a: z.string() }).safeExtend({ a: z.any() }); // ✅
z.object({ a: z.string() }).safeExtend({ a: z.number() });
//                                       ^  ❌ ZodNumber 不可分配

使用 .safeExtend() 扩展包含 refinements 的 schema。(常规 .extend() 在用于具有 refinements 的 schema 时会抛出错误。)

const Base = z.object({
  a: z.string(),
  b: z.string()
}).refine(user => user.a === user.b);
 
// Extended 继承了 Base 的 refinements
const Extended = Base.safeExtend({
  a: z.string().min(10)
});

.pick()

受 TypeScript 内置的 PickOmit 实用程序类型的启发,Zod 提供了专用的 API 来从对象 schema 中选择和省略某些键。

从这个初始 schema 开始:

const Recipe = z.object({
  title: z.string(),
  description: z.string().optional(),
  ingredients: z.array(z.string()),
});
// { title: string; description?: string | undefined; ingredients: string[] }

选择某些键:

const JustTheTitle = Recipe.pick({ title: true });

.omit()

省略某些键:

const RecipeNoId = Recipe.omit({ id: true });

.partial()

为了方便起见,Zod 提供了一个专用的 API 来使某些或所有属性可选,灵感来自内置的 TypeScript 实用程序类型 Partial

使所有字段可选:

const PartialRecipe = Recipe.partial();
// { title?: string | undefined; description?: string | undefined; ingredients?: string[] | undefined }

使某些属性可选:

const RecipeOptionalIngredients = Recipe.partial({
  ingredients: true,
});
// { title: string; description?: string | undefined; ingredients?: string[] | undefined }

.required()

Zod 提供了一个 API 来使某些或所有属性为 必需,灵感来自 TypeScript 的 Required 实用程序类型。

使所有属性为必需:

const RequiredRecipe = Recipe.required();
// { title: string; description: string; ingredients: string[] }

使某些属性为必需:

const RecipeRequiredDescription = Recipe.required({description: true});
// { title: string; description: string; ingredients: string[] }

递归对象 (Recursive objects)

要定义自引用类型,请在键上使用 getter。这允许 JavaScript在运行时解析循环 schema。

const Category = z.object({
  name: z.string(),
  get subcategories(){
    return z.array(Category)
  }
});
 
type Category = z.infer<typeof Category>;
// => { name: string; subcategories: Category[] }

虽然支持递归 schema,但将循环数据传递给 Zod 会导致无限循环。

你也可以表示 相互递归类型

const User = z.object({
  email: z.email(),
  get posts(){
    return z.array(Post)
  }
});
 
const Post = z.object({
  title: z.string(),
  get author(){
    return User
  }
});

所有对象 API (.pick(), .omit(), .required(), .partial() 等) 都如预期的那样工作。

循环错误 (Circularity errors)

由于 TypeScript 的限制,递归类型推断可能很棘手,并且仅在某些情况下有效。一些更复杂的类型可能会触发如下递归类型错误:

const Activity = z.object({
  name: z.string(),
  get subactivities() {
    // ^ ❌ 'subactivities' implicitly has return type 'any' because it does not
    // have a return type annotation and is referenced directly or indirectly
    // in one of its return expressions.ts(7023)
 
    return z.nullable(z.array(Activity));
  },
});

在这种情况下,你可以使用有问题的 getter 上的类型注释来解决错误:

const Activity = z.object({
  name: z.string(),
  get subactivities(): z.ZodNullable<z.ZodArray<typeof Activity>> {
    return z.nullable(z.array(Activity));
  },
});

数组 (Arrays)

要定义数组 schema:

const stringArray = z.array(z.string()); // or z.string().array()

要访问数组元素的内部 schema:

stringArray.unwrap(); // => string schema

Zod 实现了许多针对数组的验证:

z.array(z.string()).min(5); // 必须包含 5 个或更多项目
z.array(z.string()).max(5); // 必须包含 5 个或更少项目
z.array(z.string()).length(5); // 必须恰好包含 5 个项目

元组 (Tuples)

与数组不同,元组通常是固定长度的数组,为每个索引指定不同的 schema。

const MyTuple = z.tuple([
  z.string(),
  z.number(),
  z.boolean()
]);
 
type MyTuple = z.infer<typeof MyTuple>;
// [string, number, boolean]

要添加可变参数("rest"):

const variadicTuple = z.tuple([z.string()], z.number());
// => [string, ...number[]];

联合 (Unions)

联合类型 (A | B) 表示逻辑“或”。Zod 联合 schema 将按顺序根据每个选项检查输入。返回第一个验证成功的值。

const stringOrNumber = z.union([z.string(), z.number()]);
// string | number
 
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes

要提取内部选项 schema:

stringOrNumber.options; // [ZodString, ZodNumber]

可辨识联合 (Discriminated unions)

可辨识联合 是一种特殊的联合,其中 a) 所有选项都是对象 schema,b) 共享一个特定的键(“鉴别器”或 discriminator)。根据鉴别器键的值,TypeScript 能够像你预期的那样“缩小”类型签名。

type MyResult =
  | { status: "success"; data: string }
  | { status: "failed"; error: string };
 
function handleResult(result: MyResult){
  if(result.status === "success"){
    result.data; // string
  } else {
    result.error; // string
  }
}

你可以用常规的 z.union() 来表示它。但常规联合是 朴素的——它们按顺序根据每个选项检查输入,并返回第一个通过的选项。对于大型联合来说,这可能很慢。

因此,Zod 提供了一个 z.discriminatedUnion() API,它使用 鉴别器键 来使解析更高效。

const MyResult = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  z.object({ status: z.literal("failed"), error: z.string() }),
]);

每个选项都应该是一个 对象 schema,其鉴别器属性(上例中的 status)对应于某个字面量值或值集,通常是 z.enum()z.literal()z.null()z.undefined()

交叉类型 (Intersections)

交叉类型 (A & B) 表示逻辑“与”。

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

这对于交叉两个对象类型很有用。

const Person = z.object({ name: z.string() });
type Person = z.infer<typeof Person>;
 
const Employee = z.object({ role: z.string() });
type Employee = z.infer<typeof Employee>;
 
const EmployedPerson = z.intersection(Person, Employee);
type EmployedPerson = z.infer<typeof EmployedPerson>;
// Person & Employee

合并对象 schema 时,首选 A.extend(B) 而不是交叉。使用 .extend() 会给你一个新的对象 schema,而 z.intersection(A, B) 返回一个 ZodIntersection 实例,它缺少常见的对象方法,如 pickomit

记录 (Records)

Record schema 用于验证诸如 Record<string, string> 之类的类型。

const IdCache = z.record(z.string(), z.string());
type IdCache = z.infer<typeof IdCache>; // Record<string, string>
 
IdCache.parse({
  carlotta: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd",
  jimmie: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd",
});

Key schema 可以是任何可分配给 string | number | symbol 的 Zod schema。

const Keys = z.union([z.string(), z.number(), z.symbol()]);
const AnyObject = z.record(Keys, z.unknown());
// Record<string | number | symbol, unknown>

要创建包含由枚举定义的键的对象 schema:

const Keys = z.enum(["id", "name", "email"]);
const Person = z.record(Keys, z.string());
// { id: string; name: string; email: string }

Zod 4 — 在 Zod 4 中,如果你将 z.enum 作为第一个参数传递给 z.record(),Zod 将全面检查所有枚举值是否作为键存在于输入中。这种行为与 TypeScript 一致:

type MyRecord = Record<"a" | "b", string>;
const myRecord: MyRecord = { a: "foo", b: "bar" }; // ✅
const myRecord: MyRecord = { a: "foo" }; // ❌ missing required key `b`

在 Zod 3 中,不检查完整性。要复制旧行为,请使用 z.partialRecord()

如果你想要一个 partial(部分)记录类型,请使用 z.partialRecord()。这将跳过 Zod 通常使用 z.enum()z.literal() 键 schema 运行的特殊完整性检查。

const Keys = z.enum(["id", "name", "email"]).or(z.never()); 
const Person = z.partialRecord(Keys, z.string());
// { id?: string; name?: string; email?: string }

Maps

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);

Sets

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);

Set schema 可以使用以下实用程序方法进一步约束。

z.set(z.string()).min(5); // 必须包含 5 个或更多项目
z.set(z.string()).max(5); // 必须包含 5 个或更少项目
z.set(z.string()).size(5); // 必须恰好包含 5 个项目

文件 (Files)

要验证 File 实例:

const fileSchema = z.file();
 
fileSchema.min(10_000); // 最小 .size (bytes)
fileSchema.max(1_000_000); // 最大 .size (bytes)
fileSchema.mime("image/png"); // MIME 类型
fileSchema.mime(["image/png", "image/jpeg"]); // 多个 MIME 类型

Promises

已弃用z.promise() 在 Zod 4 中已弃用。Promise schema 的有效用例极少。如果你怀疑某个值可能是 Promise,只需在用 Zod 解析之前 await 它。

Instanceof

你可以使用 z.instanceof 来检查输入是否为类的实例。这对于针对从第三方库导出的类验证输入通过很有用。

class Test {
  name: string;
}
 
const TestSchema = z.instanceof(Test);
 
TestSchema.parse(new Test()); // ✅
TestSchema.parse("whatever"); // ❌

属性 (Property)

要针对 Zod schema 验证类实例的特定属性:

const blobSchema = z.instanceof(URL).check(
  z.property("protocol", z.literal("https:" as string, "Only HTTPS allowed"))
);
 
blobSchema.parse(new URL("https://example.com")); // ✅
blobSchema.parse(new URL("http://example.com")); // ❌

z.property() API 适用于任何数据类型(但在与 z.instanceof() 结合使用时最有用)。

const blobSchema = z.string().check(
  z.property("length", z.number().min(10))
);
 
blobSchema.parse("hello there!"); // ✅
blobSchema.parse("hello."); // ❌

Refinements

每个 Zod schema 都存储一个 refinements 数组。Refinements 是一种执行 Zod 没有为其提供原生 API 的自定义验证的方法。

.refine()

const myString = z.string().refine((val) => val.length <= 255);

Refinement 函数不应该抛出错误。相反,它们应该返回一个 falsy 值来表示失败。Zod 不会捕获抛出的错误。

error

要自定义错误消息:

const myString = z.string().refine((val) => val.length > 8, { 
  error: "Too short!" 
});

abort

默认情况下,来自 check 的验证问题被认为是 可继续的;也就是说,即使其中一个导致验证错误,Zod 也会按顺序执行 所有 check。这通常是可取的,因为这意味着 Zod 可以一次性显示尽可能多的错误。

const myString = z.string()
  .refine((val) => val.length > 8, { error: "Too short!" })
  .refine((val) => val === val.toLowerCase(), { error: "Must be lowercase" });
  
 
const result = myString.safeParse("OH NO");
result.error?.issues;
/* [
  { "code": "custom", "message": "Too short!" },
  { "code": "custom", "message": "Must be lowercase" }
] */

要将特定的 refinement 标记为 不可继续,请使用 abort 参数。如果 check 失败,验证将终止。

const myString = z.string()
  .refine((val) => val.length > 8, { error: "Too short!", abort: true })
  .refine((val) => val === val.toLowerCase(), { error: "Must be lowercase", abort: true });
 
 
const result = myString.safeParse("OH NO");
result.error?.issues;
// => [{ "code": "custom", "message": "Too short!" }]

path

要自定义错误路径,请使用 path 参数。这通常仅在对象 schema 的上下文中有用。

const passwordForm = z
  .object({
    password: z.string(),
    confirm: z.string(),
  })
  .refine((data) => data.password === data.confirm, {
    message: "Passwords don't match",
    path: ["confirm"], // path of error
  });

这将在相关 issue 中设置 path 参数:

const result = passwordForm.safeParse({ password: "asdf", confirm: "qwer" });
result.error.issues;
/* [{
  "code": "custom",
  "path": [ "confirm" ],
  "message": "Passwords don't match"
}] */

要定义异步 refinement,只需传递一个 async 函数:

const userId = z.string().refine(async (id) => {
  // verify that ID exists in database
  return true;
});

如果你使用异步 refinements,你必须使用 .parseAsync 方法来解析数据!否则 Zod 会抛出错误。

const result = await userId.parseAsync("abc123");

when

注意 — 这是一个高级用户功能,如果在 refinements 内部滥用可能会增加未捕获错误的概率。

默认情况下,如果已经遇到任何 不可继续 的问题,refinements 将不会运行。Zod 在将其传递给任何 refinement 函数之前,会仔细确保值的类型签名正确。

const schema = z.string().refine((val) => {
  return val.length > 8
});
 
schema.parse(1234); // invalid_type: refinement won't be executed

在某些情况下,你希望更精细地控制 refinements 的运行时间。例如,考虑此“密码确认”检查:

const schema = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
    anotherField: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Passwords do not match",
    path: ["confirmPassword"],
  });
 
schema.parse({
  password: "asdf",
  confirmPassword: "asdf",
  anotherField: 1234 // ❌ this error will prevent the password check from running
});

anotherField 上的错误将阻止密码确认检查执行,即使该检查不依赖于 anotherField。要控制 refinement 何时运行,请使用 when 参数:

const schema = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
    anotherField: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Passwords do not match",
    path: ["confirmPassword"],
 
    // run if password & confirmPassword are valid
    when(payload) { 
      return schema 
        .pick({ password: true, confirmPassword: true }) 
        .safeParse(payload.value).success; 
    },  
  });
 
schema.parse({
  password: "asdf",
  confirmPassword: "asdf",
  anotherField: 1234 // ❌ this error will not prevent the password check from running
});

.superRefine()

常规 .refine API 仅生成具有 "custom" 错误代码的单个 issue,但 .superRefine() 可以使用任何 Zod 内部 issue 类型 创建多个 issue。

const UniqueStringArray = z.array(z.string()).superRefine((val, ctx) => {
  if (val.length > 3) {
    ctx.addIssue({
      code: "too_big",
      maximum: 3,
      origin: "array",
      inclusive: true,
      message: "Too many items 😡",
      input: val,
    });
  }
 
  if (val.length !== new Set(val).size) {
    ctx.addIssue({
      code: "custom",
      message: `No duplicates allowed.`,
      input: val,
    });
  }
});
 

.check()

注意.check() API 是一个更底层的 API,通常比 .superRefine() 更复杂。在性能敏感的代码路径中它可能更快,但它也更冗长。

编解码器 (Codecs)

新功能 — 在 Zod 4.1 中引入。有关更多信息,请参阅专用的 Codecs 页面。

编解码器(Codecs)是一种特殊的 schema,它实现了另外两个 schema 之间的 双向转换

const stringToDate = z.codec(
  z.iso.datetime(),  // input schema: ISO date string
  z.date(),          // output schema: Date object
  {
    decode: (isoString) => new Date(isoString), // ISO string → Date
    encode: (date) => date.toISOString(),       // Date → ISO string
  }
);

常规的 .parse() 操作执行 正向转换。它调用编解码器的 decode 函数。

stringToDate.parse("2024-01-15T10:30:00.000Z"); // => Date

或者,你可以使用顶级 z.decode() 函数。与 .parse()(接受 unknown 输入)不同,z.decode() 期望强类型输入(在此示例中为 string)。

z.decode(stringToDate, "2024-01-15T10:30:00.000Z"); // => Date

要执行 反向转换,请使用逆过程:z.encode()

z.encode(stringToDate, new Date("2024-01-15")); // => "2024-01-15T00:00:00.000Z"

有关更多信息,请参阅专用的 Codecs 页面。该页面包含常用编解码器的实现,你可以将其复制/粘贴到你的项目中:

管道 (Pipes)

Schema 可以链接在一起形成“管道(pipes)”。当管道与 Transforms 结合使用时尤其有用。

const stringToLength = z.string().pipe(z.transform(val => val.length));
 
stringToLength.parse("hello"); // => 5

变换 (Transforms)

注意 — 对于双向变换,请使用 codecs

Transforms 是一种特殊的 schema,它执行单向转换。它们接受任何内容并对数据执行某些转换,而不是验证输入。要定义一个 transform:

const castToString = z.transform((val) => String(val));
 
castToString.parse("asdf"); // => "asdf"
castToString.parse(123); // => "123"
castToString.parse(true); // => "true"

Transform 函数不应该抛出错误。Zod 不会捕获抛出的错误。

要在 transform 内部执行验证逻辑,请使用 ctx。要报告验证问题,请将新问题推送到 ctx.issues(类似于 .check() API)。

const coercedInt = z.transform((val, ctx) => {
  try {
    const parsed = Number.parseInt(String(val));
    return parsed;
  } catch (e) {
    ctx.issues.push({
      code: "custom",
      message: "Not a number",
      input: val,
    });
 
    // this is a special constant with type `never`
    // returning it lets you exit the transform without impacting the inferred return type
    return z.NEVER;
  }
});

通常,transforms 与 Pipes 结合使用。这种组合对于执行一些初始验证,然后将解析后的数据转换为另一种形式非常有用。

const stringToLength = z.string().pipe(z.transform(val => val.length));
 
stringToLength.parse("hello"); // => 5

.transform()

将某些 schema 传输到 transform 中是一种常见的模式,因此 Zod 提供了一个便捷的 .transform() 方法。

const stringToLength = z.string().transform(val => val.length); 

Transforms 也可以是异步的:

const idToUser = z
  .string()
  .transform(async (id) => {
    // fetch user from database
    return db.getUserById(id); 
  });
 
const user = await idToUser.parseAsync("abc123");

如果你使用异步 transforms,你在解析数据时必须使用 .parseAsync.safeParseAsync!否则 Zod 会抛出错误。

.preprocess()

将 transform 传输到另一个 schema 是另一种常见的模式,因此 Zod 提供了一个便捷的 z.preprocess() 函数。

const coercedInt = z.preprocess((val) => {
  if (typeof val === "string") {
    return Number.parseInt(val);
  }
  return val;
}, z.int());

默认值 (Defaults)

要为 schema 设置默认值:

const defaultTuna = z.string().default("tuna");
 
defaultTuna.parse(undefined); // => "tuna"

或者,你可以传递一个函数,该函数将在需要生成默认值时重新执行:

const randomDefault = z.number().default(Math.random);
 
randomDefault.parse(undefined);    // => 0.4413456736055323
randomDefault.parse(undefined);    // => 0.1871840107401901
randomDefault.parse(undefined);    // => 0.7223408162401552

预设默认 (Prefaults)

在 Zod 中,设置 default 值将使解析过程短路。如果输入是 undefined,则会立即返回默认值。因此,默认值必须可分配给 schema 的 输出类型

const schema = z.string().transform(val => val.length).default(0);
schema.parse(undefined); // => 0

有时,定义一个 prefault(“预解析默认”)值很有用。如果输入是 undefined,则会解析 prefault 值。解析过程 不会 短路。因此,prefault 值必须可分配给 schema 的 输入类型

z.string().transform(val => val.length).prefault("tuna");
schema.parse(undefined); // => 4

如果你想通过某些变异的 refinements 传递某些输入值,这也很有用。

const a = z.string().trim().toUpperCase().prefault("  tuna  ");
a.parse(undefined); // => "TUNA"
 
const b = z.string().trim().toUpperCase().default("  tuna  ");
b.parse(undefined); // => "  tuna  "

Catch

使用 .catch() 定义在验证错误时返回的回退值:

const numberWithCatch = z.number().catch(42);
 
numberWithCatch.parse(5); // => 5
numberWithCatch.parse("tuna"); // => 42

或者,你可以传递一个函数,该函数将在需要生成 catch 值时重新执行。

const numberWithRandomCatch = z.number().catch((ctx) => {
  ctx.error; // the caught ZodError
  return Math.random();
});
 
numberWithRandomCatch.parse("sup"); // => 0.4413456736055323
numberWithRandomCatch.parse("sup"); // => 0.1871840107401901
numberWithRandomCatch.parse("sup"); // => 0.7223408162401552

品牌类型 (Branded types)

TypeScript 的类型系统是 结构化 的,这意味着两个结构等价的类型被认为是相同的。

type Cat = { name: string };
type Dog = { name: string };
 
const pluto: Dog = { name: "pluto" };
const simba: Cat = pluto; // works fine

在某些情况下,可能需要在 TypeScript 内部模拟 名义类型。这可以通过 品牌类型(branded types,也称为“不透明类型”)来实现。

const Cat = z.object({ name: z.string() }).brand<"Cat">();
const Dog = z.object({ name: z.string() }).brand<"Dog">();
 
type Cat = z.infer<typeof Cat>; // { name: string } & z.$brand<"Cat">
type Dog = z.infer<typeof Dog>; // { name: string } & z.$brand<"Dog">
 
const pluto = Dog.parse({ name: "pluto" });
const simba: Cat = pluto; // ❌ not allowed

在底层,这是通过将“品牌”附加到 schema 的推断类型来实现的。

const Cat = z.object({ name: z.string() }).brand<"Cat">();
type Cat = z.infer<typeof Cat>; // { name: string } & z.$brand<"Cat">

有了这个品牌,任何普通的(无品牌)数据结构都不能再分配给推断类型。你必须使用 schema 解析一些数据才能获得品牌数据。

注意,品牌类型不影响 .parse 的运行时结果。它只是一个静态构造。

Readonly

要将 schema 标记为只读:

const ReadonlyUser = z.object({ name: z.string() }).readonly();
type ReadonlyUser = z.infer<typeof ReadonlyUser>;
// Readonly<{ name: string }>

新 schema 的推断类型将被标记为 readonly。请注意,在 TypeScript 中,这仅影响对象、数组、元组、SetMap

z.object({ name: z.string() }).readonly(); // { readonly name: string }
z.array(z.string()).readonly(); // readonly string[]
z.tuple([z.string(), z.number()]).readonly(); // readonly [string, number]
z.map(z.string(), z.date()).readonly(); // ReadonlyMap<string, Date>
z.set(z.string()).readonly(); // ReadonlySet<string>

输入将像往常一样解析,然后结果将使用 Object.freeze() 冻结,以防止修改。

const result = ReadonlyUser.parse({ name: "fido" });
result.name = "simba"; // throws TypeError

JSON

要验证任何可 JSON 编码的值:

const jsonSchema = z.json();

这是一个方便的 API,它返回以下联合 schema:

const jsonSchema = z.lazy(() => {
  return z.union([
    z.string(params), 
    z.number(), 
    z.boolean(), 
    z.null(), 
    z.array(jsonSchema), 
    z.record(z.string(), jsonSchema)
  ]);
});

函数 (Functions)

Zod 提供了一个 z.function() 实用程序来定义 Zod 验证的函数。这样,你可以避免将验证代码与业务逻辑混合。

const MyFunction = z.function({
  input: [z.string()], // parameters (must be an array or a ZodTuple)
  output: z.number()  // return type
});
 
type MyFunction = z.infer<typeof MyFunction>;
// (input: string) => number

函数 schema 有一个 .implement() 方法,该方法接受一个函数并返回一个自动验证其输入和输出的新函数。

const computeTrimmedLength = MyFunction.implement((input) => {
  // TypeScript knows input is a string!
  return input.trim().length;
});
 
computeTrimmedLength("sandwich"); // => 8
computeTrimmedLength(" asdf "); // => 4

如果输入无效,此函数将抛出 ZodError

computeTrimmedLength(42); // throws ZodError

如果你只关心验证输入,可以省略 output 字段。

const MyFunction = z.function({
  input: [z.string()], // parameters (must be an array or a ZodTuple)
});
 
const computeTrimmedLength = MyFunction.implement((input) => input.trim.length);

使用 .implementAsync() 方法创建异步函数。

const computeTrimmedLengthAsync = MyFunction.implementAsync(
  async (input) => input.trim().length
);
 
computeTrimmedLengthAsync("sandwich"); // => Promise<8>

自定义 (Custom)

你可以使用 z.custom() 为任何 TypeScript 类型创建 Zod schema。这对于为 Zod 开箱即用不支持的类型创建 schema 非常有用,例如模板字符串字面量。

const px = z.custom<`${number}px`>((val) => {
  return typeof val === "string" ? /^\d+px$/.test(val) : false;
});
 
type px = z.infer<typeof px>; // `${number}px`
 
px.parse("42px"); // "42px"
px.parse("42vw"); // throws;

如果你不提供验证函数,Zod 将允许任何值。这可能很危险!

z.custom<{ arg: string }>(); // performs no validation

你可以通过传递第二个参数来自定义错误消息和其他选项。此参数的工作方式与 .refine 的 params 参数相同。

z.custom<...>((val) => ..., "custom error message");