💎 Zod 4 现已稳定发布! 阅读公告。
Zod logo

定义模式

Edit this page

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

原始类型

import * as z from "zod";
 
// 原始类型
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();

强制转换

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

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

这些模式的强制转换版本尝试将输入值转换为适合的类型。

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

这些强制转换模式的输入类型默认为 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

字面量

字面量模式表示一种 字面量类型,例如 "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"); // ❌

从字面量模式中提取允许值集合:

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

字符串

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 字符

字符串格式

验证常见字符串格式:

z.email();
z.uuid();
z.url();
z.httpUrl();       // http or https URLs only
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 block
z.cidrv6();        // ipv6 CIDR block
z.hash("sha256");  // or "sha1", "sha384", "sha512", "md5"
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();

邮件

验证邮箱地址:

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

The RFC 9562/4122 UUID spec requires the first two bits of byte 8 to be 10. Other UUID-like identifiers do not enforce this constraint. To validate any UUID-like identifier:

z.guid();

URL

验证任意 WHATWG 兼容 URL:

const schema = z.url();
 
schema.parse("https://example.com"); // ✅
schema.parse("http://localhost"); // ✅
schema.parse("mailto:noreply@zod.dev"); // ✅

如您所见,这非常宽松。内部使用 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 URLs — 许多情况下,您可能只想验证 Web URL。推荐的模式如下:

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,}$/

要规范化 URL,请使用 normalize 标志。这将用 new URL() 返回的 规范化 URL 覆盖输入值。

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

ISO 日期时间

正如您可能注意到的,Zod 字符串包含了一些与日期/时间相关的验证。这些验证是基于正则表达式的,因此它们没有完整的日期/时间库那么严格。然而,它们对于验证用户输入非常方便。

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"); // ✅ (arbitrary precision)
datetime.parse("2020-01-01T06:15:00+02:00"); // ❌ (offsets not allowed)
datetime.parse("2020-01-01T06:15:00"); // ❌ (local not allowed)

允许时区偏移:

const datetime = z.iso.datetime({ offset: true });
 
// allows timezone offsets
datetime.parse("2020-01-01T06:15:00+02:00"); // ✅
 
// basic offsets not allowed
datetime.parse("2020-01-01T06:15:00+02");    // ❌
datetime.parse("2020-01-01T06:15:00+0200");  // ❌
 
// Z is still supported
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"); // ✅ seconds optional

限制允许的时间 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 }); // minute precision (no seconds)
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 }); // second precision only
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 }); // millisecond precision only
d.parse("2020-01-01T06:15Z"); // ❌
d.parse("2020-01-01T06:15:00Z"); // ❌
d.parse("2020-01-01T06:15:00.123Z"); // ✅

ISO 日期

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 时间

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 地址

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 Addresses

Validate standard 48-bit MAC address IEEE 802.

const mac = z.mac(); 
mac.parse("00:1A:2B:3C:4D:5E");  // ✅
mac.parse("00-1a-2b-3c-4d-5e");  // ❌ colon-delimited by default
mac.parse("001A:2B3C:4D5E");     // ❌ standard formats only
mac.parse("00:1A:2b:3C:4d:5E");  // ❌ no mixed case
 
// custom delimiter
const dashMac = z.mac({ delimiter: "-" });
dashMac.parse("00-1A-2B-3C-4D-5E"); // ✅

JWTs

验证 JSON Web Tokens

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

哈希

验证加密哈希值:

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

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

z.hash("sha256", { enc: "hex" });       // default
z.hash("sha256", { enc: "base64" });    // base64 encoding
z.hash("sha256", { enc: "base64url" }); // base64url encoding (no padding)

自定义格式

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

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

此模式将产生 "invalid_format" 问题,这些问题比通过细化或 z.custom() 产生的 "custom" 错误更具描述性。

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

模板字面量

— 在 zod@4.0 中引入。

定义模板字面量模式:

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

z.templateLiteral API 可以处理任意数量的字符串字面量(如 "hello")和模式。可传入任何推断类型可赋值给string | number | bigint | boolean | null | undefined的模式。

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`

数字

使用 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);                     // 同 .min(5)
z.number().lt(5);
z.number().lte(5);                     // 同 .max(5)
z.number().positive();       
z.number().nonnegative();    
z.number().negative(); 
z.number().nonpositive(); 
z.number().multipleOf(5);              // 同 .step(5)

如果(因某些原因)想验证 NaN,使用 z.nan()

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

整数

验证整数:

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

BigInt

验证 BigInts:

z.bigint();

Zod 提供了一些针对 bigint 的验证。

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

布尔值

验证布尔值:

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

日期

使用 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!" });

枚举

使用 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

提取模式的值为类似枚举的对象:

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

.exclude()

创建新枚举模式,排除指定值:

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

.extract()

创建新枚举模式,仅包含指定值:

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

字符串布尔值

💎 Zod 4 新增

某些情况下(如解析环境变量),将某些字符串“布尔值”解析为纯 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(/* 其他任何值 */); // ZodError<[{ code: "invalid_value" }]>

自定义真值和假值:

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

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

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

可选

使模式可选(即允许 undefined 输入)。

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

返回一个 ZodOptional 实例,包装了原模式。提取内层模式:

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

可空

使模式可空(即允许 null 输入)。

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

返回一个 ZodNullable 实例,包装了原模式。提取内层模式:

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

Nullish

使模式nullish(即可选且可空):

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

更多关于nullish概念,请参阅 TypeScript 手册。

Unknown

Zod 致力于与 TypeScript 类型系统一一对应,因此提供以下特殊类型的 API:

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

Never

没有任何值可以通过验证。

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

对象

定义对象类型:

  // 所有属性默认必需
  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

定义严格模式,检测到未知键时报错:

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

z.looseObject

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

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

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

.shape

访问内部模式:

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

.keyof()

从对象模式的键创建 ZodEnum 模式:

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

.extend()

为对象模式添加额外字段:

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

此 API 可用于覆盖现有字段!请小心使用此功能!如果两个模式共享键,B 将覆盖 A。

Alternative: spread syntax — You can alternatively avoid .extend() altogether by creating a new object schema entirely. This makes the strictness level of the resulting schema visually obvious.

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() 方法可能会很昂贵,并且由于 TypeScript 的限制,当调用链式调用时,它的成本会呈平方增长
  4. 如果您愿意,可以通过使用 z.strictObject()z.looseObject() 来更改结果模式的严格性级别

.safeExtend()

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

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 is not assignable 

使用 .safeExtend() 来扩展包含细化的模式。(常规的 .extend() 在用于包含细化的模式时会抛出错误。)

const Base = z.object({
  a: z.string(),
  b: z.string()
}).refine(user => user.a === user.b);
 
// Extended inherits the refinements of Base
const Extended = Base.safeExtend({
  a: z.string().min(10)
});

.pick()

借鉴 TypeScript 的内置工具类型 PickOmit,Zod 提供了专用 API 用于从对象模式中选择/剔除某些键。

初始模式:

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[] }

递归对象

定义自引用类型,使用键的getter。这让 JavaScript 在运行时解析循环模式。

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

虽支持递归模式,但传入循环数据将导致无限循环。

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

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() 等)均如预期工作。

循环错误

由于 TypeScript 限制,递归类型推断可能出现问题,只在某些场景有效。复杂类型可能触发如下递归类型错误:

const Activity = z.object({
  name: z.string(),
  get subactivities() {
    // ^ ❌ 'subactivities' 隐式返回值类型为 'any',因无返回类型注解且直接或间接自身引用.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));
  },
});

数组

定义数组模式:

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

访问数组元素的内层模式:

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 项

元组

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

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

添加可变长("剩余")参数:

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

联合类型

联合类型(A | B)代表逻辑“或”。Zod 联合模式依次检查输入,返回第一个验证成功的选项。

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

访问内部选项模式:

stringOrNumber.options; // [ZodString, ZodNumber]

排他联合(XOR)

排他联合(XOR)是一种联合,其中必须恰好有一个选项匹配。与普通联合只要有任意一个选项匹配就成功不同,z.xor()在没有选项匹配或有多个选项匹配时都会失败。

const schema = z.xor([z.string(), z.number()]);
 
schema.parse("hello"); // ✅ passes
schema.parse(42);      // ✅ passes
schema.parse(true);    // ❌ fails (zero matches)

当你想确保选项之间互斥时,这很有用:

// Validate that exactly ONE of these matches
const payment = z.xor([
  z.object({ type: z.literal("card"), cardNumber: z.string() }),
  z.object({ type: z.literal("bank"), accountNumber: z.string() }),
]);
 
payment.parse({ type: "card", cardNumber: "1234" }); // ✅ passes

如果输入可以匹配多个选项,z.xor() 将会失败:

const overlapping = z.xor([z.string(), z.any()]);
overlapping.parse("hello"); // ❌ fails (matches both string and any)

带分辨字段的联合

带分辨字段的联合 是一种特殊联合,要求 a) 所有选项均为对象模式且 b) 共享特定键(“分辨字段”)。根据分辨字段的值,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(),通过分辨字段提高解析效率。

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

每个选项应该是一个 对象模式,其区分符属性(上面示例中的 status)对应某个字面值或一组值,通常是 z.enum()z.literal()z.null()z.undefined()

交叉类型

交叉类型 (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

在合并对象模式时,优先使用 A.extend(B) 而不是交集。使用 .extend() 将为您提供一个新的对象模式,而 z.intersection(A, B) 返回一个 ZodIntersection 实例,该实例缺少像 pickomit 这样的公共对象方法。

Record 类型

Record 模式用于验证如 Record<string, string> 类型。

z.record

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",
});

键模式可为任何可赋值给 string | number | symbol 的 Zod 模式。

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

使用枚举定义键的对象模式:

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

z.partialRecord

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" }; // ❌ 缺少必需键 `b`

在 Zod 3 中,没有检查穷尽性。要复制旧的行为,请使用 z.partialRecord()

若需定义部分 Record 类型,使用 z.partialRecord() 。此方法跳过 Zod 对 z.enum()z.literal() 键模式的详尽检查。

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

z.looseRecord

By default, z.record() errors on keys that don't match the key schema. Use z.looseRecord() to pass through non-matching keys unchanged. This is particularly useful when combined with intersections to model multiple pattern properties:

const schema = z.object({ name: z.string() }).passthrough()
  .and(z.looseRecord(z.string().regex(/^S_/), z.string()))
  .and(z.looseRecord(z.string().regex(/^N_/), z.number()));
 
schema.parse({ 
  name: "John",
  other: "value",    // passes through unchanged
  S_foo: "bar",     // validated as string
  N_count: 123,     // validated as number
});

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

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

Set 模式可用以下方法进一步约束。

z.set(z.string()).min(5); // 至少 5 个元素
z.set(z.string()).max(5); // 最多 5 个元素
z.set(z.string()).size(5); // 恰好 5 个元素

文件

验证 File 实例:

const fileSchema = z.file();
 
fileSchema.min(10_000); // minimum .size (bytes)
fileSchema.max(1_000_000); // maximum .size (bytes)
fileSchema.mime("image/png"); // MIME type
fileSchema.mime(["image/png", "image/jpeg"]); // multiple MIME types

Promise

已弃用z.promise() 在 Zod 4 中已弃用。有效的使用场景极少。若怀疑值是 Promise,应在 Zod 验证前先 await

instanceof 验证

使用 z.instanceof 验证输入是否为指定类的实例。适合验证来自第三方库的类实例。

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

属性

要验证类实例的特定属性是否符合 Zod 模式:

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

精炼验证

每个 Zod 模式都存有一组精炼数组。精炼用于执行 Zod 本身无内置 API 的自定义验证。

.refine()

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

精炼函数不应抛异常,且应返回假值表示失败。抛出错误不被 Zod 捕获。

error

要自定义错误消息:

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

abort

默认情况下,验证问题视为可继续,即所有检查都会依次执行,即便前面发生错误。此行为通常有利,可一次发现尽可能多错误。

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" }
] */

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

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 参数。这通常仅在对象模式的上下文中有用。

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

这会将 path 设置为相关问题:

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

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

const userId = z.string().refine(async (id) => {
  // 验证 ID 是否存在数据库
  return true;
});

若使用异步精炼,解析数据时必须调用 .parseAsync ,否则 Zod 会抛错。

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

when

注意 — 这是一个高级用户功能,绝对可能被滥用,从而增加未捕获错误的概率,这些错误源自于您的修饰。

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

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

在某些情况下,您希望更精细地控制何时运行优化。例如,考虑这个“密码确认”检查:

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

An error on anotherField will prevent the password confirmation check from executing, even though the check doesn't depend on anotherField. To control when a refinement will run, use the when parameter:

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" 错误代码的问题,但 .superRefine() 使得使用 Zod 的任何 内部问题类型 创建多个问题成为可能。

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

Note — The .check() API is a more low-level API that's generally more complex than .superRefine(). It can be faster in performance-sensitive code paths, but it's also more verbose.

编解码器

— 在 Zod 4.1 中引入。有关更多信息,请参阅专门的 编解码器 页面。

编解码器是一种特殊的模式,实现在两个其他模式之间的 双向转换

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() 函数。与接受 unknown 输入的 .parse() 不同,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"

请参阅专门的 编解码器 页面以获取更多信息。该页面包含您可以复制/粘贴到项目中的常用编解码器的实现:

管道

模式可以链接在一起形成“管道”。管道在与转换结合使用时特别有用。

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

变换(Transforms)

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

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

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

转换函数不应抛出异常。Zod不会捕获抛出的错误。

在变换中执行验证逻辑,使用 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,
    });
 
    // 这是类型为 `never` 的特殊常量
    // 返回它可退出变换且不影响推断返回类型
    return z.NEVER;
  }
});

变换通常与管道联合使用。此组合方便在执行初步验证后,将解析数据转换为另一形式。

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

.transform()

将管道输入转换,常用模式,Zod 提供 .transform() 便捷方法。

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

变换可异步:

const idToUser = z
  .string()
  .transform(async (id) => {
    // 从数据库获取用户
    return db.getUserById(id); 
  });
 
const user = await idToUser.parseAsync("abc123");

若使用异步变换,解析数据时必须用 .parseAsync.safeParseAsync,否则 Zod 会抛错。

.preprocess()

将变换管道至另一个模式也常见,Zod 提供 z.preprocess() 便捷函数。

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

默认值

为模式设置默认值:

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 中,设置默认值会短路解析。若输入为 undefined,会立即返回默认值。因此默认值必须赋值给模式的输出类型

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

有时定义预故障值(“预解析默认”)更实用。若输入为 undefined,将替代性地先解析预故障值,解析过程短路。因此,预故障值必须赋值给模式的输入类型

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

若希望在一些变异型精炼中使用输入值,此法有用。

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

或传入函数,每次生成捕获值时执行:

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

品牌类型

TypeScript 类型系统是结构化的,结构相同的类型被视为相同。

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

某些情况中,想在 TypeScript 内模拟标称类型,可用品牌类型(又称“不透明类型”)。

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; // ❌ 不允许

其原理是为模式推断类型附加“品牌”。

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

使用此品牌后,任何普通(无品牌)数据结构都不再可分配给推断类型。你必须使用模式解析一些数据,以获得带品牌的数据。

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

默认情况下,只有输出类型是带品牌的。

const USD = z.string().brand<"USD">();
 
type USDOutput = z.output<typeof USD>; // string & z.$brand<"USD">
type USDInput = z.input<typeof USD>; // string

要自定义此项,请向 .brand() 传递第二个泛型以指定品牌的方向。

// requires Zod 4.2+
z.string().brand<"Cat", "in">(); // output is branded (default)
z.string().brand<"Cat", "out">(); // input is branded
z.string().brand<"Cat", "inout">(); // both are branded

只读

使模式只读:

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

新模式推断类型会标记为 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"; // 抛出 TypeError

JSON

验证任意 JSON 可编码值:

const jsonSchema = z.json();

此为便捷 API,返回如下联合模式:

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

函数

Zod 提供 z.function() 工具定义经过 Zod 验证的函数。避免验证码混入业务逻辑。

const MyFunction = z.function({
  input: [z.string()], // 参数(必须为数组或 ZodTuple)
  output: z.number()  // 返回值类型
});
 
type MyFunction = z.infer<typeof MyFunction>;
// (input: string) => number

函数模式拥有 .implement() 方法,接受函数,返回自动验证输入输出的新函数。

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

输入验证失败时抛 ZodError

computeTrimmedLength(42); // 抛出 ZodError

若只关心验证输入,可省略 output 字段。

const MyFunction = z.function({
  input: [z.string()], // 参数(必须是数组或 ZodTuple)
});
 
const computeTrimmedLength = MyFunction.implement((input) => input.trim.length);

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

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

自定义

您可以通过使用 z.custom() 为任何 TypeScript 类型创建 Zod 模式。这对于为 Zod 默认不支持的类型(例如模板字符串字面量)创建模式非常有用。

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