Zod logo

定义模式

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

¥To validate data, you must first define a schema. Schemas represent types, from simple primitive values to complex nested objects and arrays.

原语

¥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

¥To coerce input data to the appropriate type, use z.coerce instead:

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

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

¥The coerced variant of these schemas attempts to convert the input value to the appropriate type.

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

字面量

¥Literals

字面量模式代表 字面类型,例如 "hello world"5

¥Literal schemas represent a literal type, like "hello world" or 5.

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

要表示 JavaScript 字面量 nullundefined

¥To represent the JavaScript literals null and undefined:

z.null();
z.undefined();
z.void(); // equivalent to z.undefined()

要允许多个字面量值:

¥To allow multiple literal values:

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

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

¥To extract the set of allowed values from a literal schema:

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

字符串

¥Strings

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

¥Zod provides a handful of built-in string validation and transform APIs. To perform some common string validations:

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

要执行一些简单的字符串转换:

¥To perform some simple string transforms:

z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toUpperCase

字符串格式

¥String formats

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

¥To validate against some common string formats:

z.email();
z.uuid();
z.url();
z.emoji();         // validates a single emoji character
z.base64();
z.base64url();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.cidrv4();        // ipv4 CIDR block
z.cidrv6();        // ipv6 CIDR block
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();

电子邮件

¥Emails

要验证电子邮件地址:

¥To validate email addresses:

z.email();

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

¥By default, Zod uses a comparatively strict email regex designed to validate normal email addresses containing common characters. It's roughly equivalent to the rules enforced by Gmail. To learn more about this regex, refer to this post.

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

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

¥To customize the email validation behavior, you can pass a custom regular expression to the pattern param.

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

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

¥Zod exports several useful regexes you could use.

// Zod's default email regex
z.email();
z.email({ pattern: z.regexes.email }); // equivalent
 
// the regex used by browsers to validate input[type=email] fields
// https://web.nodejs.cn/en-US/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });
 
// the classic emailregex.com regex (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });
 
// a loose regex that allows Unicode (good for intl emails)
z.email({ pattern: z.regexes.unicodeEmail });

UUIDs

要验证 UUID:

¥To validate UUIDs:

z.uuid();

要指定特定的 UUID 版本:

¥To specify a particular UUID version:

// supports "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"
z.uuid({ version: "v4" });
 
// for convenience
z.uuidv4();
z.uuidv6();
z.uuidv7();

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

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

URLs

要验证任何与 WHATWG 兼容的 URL:

¥To validate any WHATWG-compatible URL:

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

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

¥As you can see this is quite permissive. Internally this uses the new URL() constructor to validate inputs; this behavior may differ across platforms and runtimes but it's the mostly rigorous way to validate URIs/URLs on any given JS runtime/engine.

要根据特定的正则表达式验证主机名:

¥To validate the hostname against a specific regex:

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

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

¥To validate the protocol against a specific regex, use the protocol param.

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

Web URL — 在许多情况下,你需要专门验证 Web URL。以下是推荐的架构:

¥Web URLs — In many cases, you'll want to validate Web URLs specifically. Here's the recommended schema for doing so:

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

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

¥This restricts the protocol to http/https and ensures the hostname is a valid domain name with the z.regexes.domain regular expression:

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

ISO 日期时间

¥ISO datetimes

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

¥As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input.

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

¥The z.iso.datetime() method enforces ISO 8601; by default, no timezone offsets are allowed:

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)

要允许时区偏移:

¥To allow timezone offsets:

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

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

¥To allow unqualified (timezone-less) datetimes:

const schema = z.iso.datetime({ local: true });
schema.parse("2020-01-01T06:15:01"); // ✅
schema.parse("2020-01-01T06:15"); // ✅ seconds optional

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

¥To constrain the allowable time precision. By default, seconds are optional and arbitrary sub-second precision is allowed.

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 日期

¥ISO dates

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

¥The z.iso.date() method validates strings in the format 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+]] 格式的字符串。默认情况下,秒是可选的,亚秒级精度也是可选的。

¥The z.iso.time() method validates strings in the format HH:MM[:SS[.s+]]. By default seconds are optional, as are sub-second deciams.

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

不允许任何形式的偏移。

¥No offsets of any kind are allowed.

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

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

¥Use the precision parameter to constrain the allowable decimal 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)

¥IP blocks (CIDR)

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

¥Validate IP address ranges specified with CIDR notation.

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

模板字面量

¥Template literals

Zod 4 中的新功能

¥New in Zod 4

定义一个模板字面量模式:

¥To define a template literal schema:

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

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

¥The z.templateLiteral API can handle any number of string literals (e.g. "hello") and schemas. Any schema with an inferred type that's assignable to string | number | bigint | boolean | null | undefined can be passed.

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() 验证数字。它允许任意有限数字。

¥Use z.number() to validate numbers. It allows any finite number.

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

Zod 实现了一些特定于数字的验证:

¥Zod implements a handful of number-specific validations:

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

¥If (for some reason) you want to validate NaN, use z.nan().

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

整数

¥Integers

要验证整数:

¥To validate integers:

z.int();     // restricts to safe integer range
z.int32();   // restrict to int32 range

BigInts

要验证 BigInt:

¥To validate BigInts:

z.bigint();

Zod 包含一些特定于 bigint 的验证。

¥Zod includes a handful of bigint-specific validations.

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

要验证布尔值:

¥To validate boolean values:

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

日期

¥Dates

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

¥Use z.date() to validate Date instances.

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

要自定义错误消息:

¥To customize the error message:

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

Zod 提供了一些特定于日期的验证。

¥Zod provides a handful of date-specific validations.

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

枚举

¥Enums

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

¥Use z.enum to validate inputs against a fixed set of allowable string values.

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

注意 - 如果将字符串数组声明为变量,Zod 将无法正确推断每个元素的确切值。

¥Careful — If you declare your string array as a variable, Zod won't be able to properly infer the exact values of each element.

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

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

¥To fix this, always pass the array directly into the z.enum() function, or use as const.

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

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

¥You can also pass in an externally-declared TypeScript enum.

Zod 4 — 这将取代 Zod 3 中的 z.nativeEnum() API。

¥Zod 4 — This replaces the z.nativeEnum() API in Zod 3.

请注意,使用 TypeScript 的 enum 关键字会得到 不推荐

¥Note that using TypeScript's enum keyword is not recommended.

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

.enum

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

¥To extract the schema's values as an enum-like object:

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

.exclude()

要创建新的枚举架构并排除某些值:

¥To create a new enum schema, excluding certain values:

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

.extract()

要创建新的枚举架构并提取某些值:

¥To create a new enum schema, extracting certain values:

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

字符串布尔值

¥Stringbools

💎 Zod 4 中的新功能

¥💎 New in Zod 4

在某些情况下(例如解析环境变量),将某些字符串 "boolish" 值解析为普通的 boolean 值很有价值。为了支持这一点,Zod 4 引入了 z.stringbool()

¥In some cases (e.g. parsing environment variables) it's valuable to parse certain string "boolish" values to a plain boolean value. To support this, Zod 4 introduces 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" }]>

自定义真值和假值:

¥To customize the truthy and falsy values:

// these are the defaults
z.stringbool({
  truthy: ["true", "1", "yes", "on", "y", "enabled"],
  falsy: ["false", "0", "no", "off", "n", "disabled"],
});

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

¥Be default the schema is case-insensitive; all inputs are converted to lowercase before comparison to the truthy/falsy values. To make it case-sensitive:

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

可选值

¥Optionals

要使架构可选(即允许 undefined 输入)。

¥To make a schema optional (that is, to allow undefined inputs).

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

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

¥This returns a ZodOptional instance that wraps the original schema. To extract the inner schema:

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

可空值

¥Nullables

要使架构可空(即允许 null 输入)。

¥To make a schema nullable (that is, to allow null inputs).

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

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

¥This returns a ZodNullable instance that wraps the original schema. To extract the inner schema:

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

将空值转换为空值

¥Nullish

要使架构可空(可选且可空):

¥To make a schema nullish (both optional and nullable):

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

记录变得更加智能。

¥Refer to the TypeScript manual for more about the concept of nullish.

未知

¥Unknown

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

¥Zod aims to mirror TypeScript's type system one-to-one. As such, Zod provides APIs to represent the following special types:

// allows any values
z.any(); // inferred type: `any`
z.unknown(); // inferred type: `unknown`

从不

¥Never

任何值都无法通过验证。

¥No value will pass validation.

z.never(); // inferred type: `never`

对象

¥Objects

定义一个对象类型:

¥To define an object type:

  // all properties are required by default
  const Person = z.object({
    name: z.string(),
    age: z.number(),
  });
 
  type Person = z.infer<typeof Person>;
  // => { name: string; age: number; }

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

¥By default, all properties are required. To make certain properties optional:

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

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

¥By default, unrecognized keys are stripped from the parsed result:

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

z.strictObject

定义一个在发现未知键时抛出错误的严格模式:

¥To define a strict schema that throws an error when unknown keys are found:

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

z.looseObject

定义一个允许未知键通过的宽松模式:

¥To define a loose schema that allows unknown keys to pass through:

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

.catchall()

要定义一个用于验证任何无法识别的键的通用模式:

¥To define a catchall schema that will be used to validate any unrecognized keys:

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

访问内部模式:

¥To access the internal schemas:

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

.keyof()

要从对象架构的键创建 ZodEnum 架构:

¥To create a ZodEnum schema from the keys of an object schema:

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

.extend()

要向对象架构添加其他字段:

¥To add additional fields to an object schema:

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

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

¥This API can be used to overwrite existing fields! Be careful with this power! If the two schemas share keys, B will override A.

替代方案:解构 - 你也可以通过创建一个全新的对象模式来完全避免使用 .extend()。这使得生成的 schema 的严格级别在视觉上显而易见。

¥Alternative: destructuring — 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(),
});

你也可以使用此功能一次性合并多个对象。

¥You can also use this to merge multiple objects in one go.

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

这种方法有几个优点:

¥This approach has a few advantages:

  1. 它使用语言级功能 (解构语法),而非特定于库的 API

    ¥It uses language-level features (destructuring syntax) instead of library-specific APIs

  2. 相同的语法适用于 Zod 和 Zod Mini

    ¥The same syntax works in Zod and Zod Mini

  3. 它更高效地执行 tsc 操作 - .extend() 方法在大型架构上可能开销较大,而由于 TypeScript 的限制 的原因,当调用链式调用时,开销会呈二次方增长。

    ¥It's more tsc-efficient — the .extend() method can be expensive on large schemas, and due to a TypeScript limitation it gets quadratically more expensive when calls are chained

  4. 如果你愿意,可以使用 z.strictObject()z.looseObject() 更改生成模式的严格级别。

    ¥If you wish, you can change the strictness level of the resulting schema by using z.strictObject() or z.looseObject()

.pick()

受 TypeScript 内置 PickOmit 工具类型的启发,Zod 提供了专用 API,用于从对象模式中选择和省略某些键。

¥Inspired by TypeScript's built-in Pick and Omit utility types, Zod provides dedicated APIs for picking and omitting certain keys from an object schema.

从此初始模式开始:

¥Starting from this initial schema:

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

要选择某些键:

¥To pick certain keys:

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

.omit()

要省略某些键:

¥To omit certain keys:

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

.partial()

为了方便起见,Zod 提供了一个专用 API,用于将部分或全部属性设为可选属性,其灵感来自内置的 TypeScript 工具类型 Partial

¥For convenience, Zod provides a dedicated API for making some or all properties optional, inspired by the built-in TypeScript utility type Partial.

要使所有字段可选:

¥To make all fields optional:

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

要使某些属性可选:

¥To make certain properties optional:

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

.required()

Zod 提供了一个 API,用于将部分或所有属性设为必需,这受到了 TypeScript 的 Required 工具类型的启发。

¥Zod provides an API for making some or all properties required, inspired by TypeScript's Required utility type.

要使所有属性必填:

¥To make all properties required:

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

要使某些属性成为必需属性:

¥To make certain properties required:

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

递归对象

¥Recursive objects

定义一个自引用类型,在键上使用 getter。这允许 JavaScript 在运行时解析循环模式。

¥To define a self-referential type, use a getter on the key. This lets JavaScript resolve the cyclical schema at runtime.

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 会导致无限循环。

¥Though recursive schemas are supported, passing cyclical data into Zod will cause an infinite loop.

你还可以表示相互递归的类型:

¥You can also represent mutually recursive types:

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

¥All object APIs (.pick(), .omit(), .required(), .partial(), etc.) work as you'd expect.

循环错误

¥Circularity errors

由于 TypeScript 的限制,递归类型推断可能比较复杂,并且仅在特定场景下有效。一些更复杂的类型可能会触发递归类型错误,如下所示:

¥Due to TypeScript limitations, recursive type inference can be finicky, and it only works in certain scenarios. Some more complicated types may trigger recursive type errors like this:

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 上添加类型注释来解析错误:

¥In these cases, you can resolve the error with a type annotation on the offending getter:

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

数组

¥Arrays

定义一个数组模式:

¥To define an array schema:

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

访问数组元素的内部模式。

¥To access the inner schema for an element of the array.

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

Zod 实现了一些特定于数组的验证:

¥Zod implements a number of array-specific validations:

z.array(z.string()).min(5); // must contain 5 or more items
z.array(z.string()).max(5); // must contain 5 or fewer items
z.array(z.string()).length(5); // must contain 5 items exactly

元组

¥Tuples

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

¥Unlike arrays, tuples are typically fixed-length arrays that specify different schemas for each index.

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

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

¥To add a variadic ("rest") argument:

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

联合

¥Unions

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

¥Union types (A | B) represent a logical "OR". Zod union schemas will check the input against each option in order. The first value that validates successfully is returned.

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

要提取内部选项模式:

¥To extract the internal option schemas:

stringOrNumber.options; // [ZodString, ZodNumber]

可区分联合

¥Discriminated unions

可区分联合 是一种特殊的联合,其中 a) 所有选项都是对象模式,并且 b) 共享一个特定的键("discriminator")。根据 discriminator 键的值,TypeScript 能够如你所愿对类型签名进行 "narrow" 验证。

¥A discriminated union is a special kind of union in which a) all the options are object schemas that b) share a particular key (the "discriminator"). Based on the value of the discriminator key, TypeScript is able to "narrow" the type signature as you'd expect.

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() 来表示它。但常规的联合操作比较简单 - 它们按顺序根据每个选项检查输入,并返回第一个通过的选项。对于大型联合来说,这可能会很慢。

¥You could represent it with a regular z.union(). But regular unions are naive—they check the input against each option in order and return the first one that passes. This can be slow for large unions.

因此,Zod 提供了一个使用鉴别键的 z.discriminatedUnion() API,以提高解析效率。

¥So Zod provides a z.discriminatedUnion() API that uses a discriminator key to make parsing more efficient.

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

交叉点

¥Intersections

交叉类型 (A & B) 表示逻辑上的 "AND"。

¥Intersection types (A & B) represent a logical "AND".

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

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

¥This can be useful for intersecting two object types.

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() 将为你提供一个新的对象 schema,而 z.intersection(A, B) 将返回一个 ZodIntersection 实例,它缺少像 pickomit 这样的通用对象方法。

¥When merging object schemas, prefer A.extend(B) over intersections. Using .extend() will gve you a new object schema, whereas z.intersection(A, B) returns a ZodIntersection instance which lacks common object methods like pick and omit.

记录

¥Records

阅读 Record<string, number> 了解更多信息。

¥Record schemas are used to validate types such as Record<string, number>.

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 模式。

¥The key schema can be any Zod schema that is assignable to string | number | symbol.

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

要创建包含枚举定义的键的对象架构:

¥To create an object schemas containing keys defined by an enum:

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 一致:

¥Zod 4 — In Zod 4, if you pass a z.enum as the first argument to z.record(), Zod will exhaustively check that all enum values exist in the input as keys. This behavior agrees with 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()

¥In Zod 3, exhaustiveness was not checked. To replicate the old behavior, use z.partialRecord().

如果你需要部分记录类型,请使用 z.partialRecord()。这将跳过 Zod 通常使用 z.enum()z.literal() 密钥模式运行的特殊详尽性检查。

¥If you want a partial record type, use z.partialRecord(). This skips the special exhaustiveness checks Zod normally runs with z.enum() and z.literal() key schemas.

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

可以使用以下实用方法进一步约束设置的 Schema。

¥Set schemas can be further constrained with the following utility methods.

z.set(z.string()).min(5); // must contain 5 or more items
z.set(z.string()).max(5); // must contain 5 or fewer items
z.set(z.string()).size(5); // must contain 5 items exactly

文件

¥Files

要验证 File 实例:

¥To validate File instances:

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 模式的有效用例极其有限。如果你怀疑某个值可能是 Promise,只需在使用 Zod 解析之前对其进行 await 转换即可。

¥Deprecatedz.promise() is deprecated in Zod 4. There are vanishingly few valid uses cases for a Promise schema. If you suspect a value might be a Promise, simply await it before parsing it with Zod.

Instanceof

你可以使用 z.instanceof 检查输入是否是某个类的实例。这对于针对从第三方库导出的类验证输入非常有用。

¥You can use z.instanceof to check that the input is an instance of a class. This is useful to validate inputs against classes that are exported from third-party libraries.

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

细化

¥Refinements

每个 Zod 模式都存储一个改进数组。细化是一种执行自定义验证的方法,Zod 并未提供原生 API。

¥Every Zod schema stores an array of refinements. Refinements are a way to perform custom validation that Zod doesn't provide a native API for.

.refine()

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

细化函数不应该抛出异常。它们应该返回一个假值来表示失败。Zod 无法捕获抛出的错误。

¥Refinement functions should never throw. Instead they should return a falsy value to signal failure. Thrown errors are not caught by Zod.

error

要自定义错误消息:

¥To customize the error message:

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

abort

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

¥By default, validation issues from checks are considered continuable; that is, Zod will execute all checks in sequence, even if one of them causes a validation error. This is usually desirable, as it means Zod can surface as many errors as possible in one go.

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 参数。如果检查失败,验证将终止。

¥To mark a particular refinement as non-continuable, use the abort parameter. Validation will terminate if the check fails.

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

¥To customize the error path, use the path parameter. This is typically only useful in the context of object schemas.

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

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

¥This will set the path parameter in the associated issue:

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

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

¥To define an asynchronous refinement, just pass an async function:

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

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

¥If you use async refinements, you must use the .parseAsync method to parse data! Otherwise Zod will throw an error.

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

when

注意 — 这是一项高级用户功能,绝对可能被滥用,从而增加源自改进内部的未捕获错误的概率。

¥— This is a power user feature and can absolutely be abused in ways that will increase the probability of uncaught errors originating from inside your refinements.

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

¥By default, refinements don't run if any non-continuable issues have already been encountered. Zod is careful to ensure the type signature of the value is correct before passing it into any refinement functions.

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

在某些情况下,你希望更精细地控制优化运行的时间。例如,请考虑以下 "密码确认" 检查:

¥In some cases, you want finer control over when refinements run. For instance consider this "password confirm" check:

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。要控制优化的运行时间,请使用 when 参数:

¥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 prevent the password check from running
});

.superRefine()

在 Zod 4 中,.superRefine() 已被弃用,取而代之的是 .check()

¥In Zod 4, .superRefine() has been deprecated in favor of .check()

.check()

.refine() API 是更通用(且冗长)的 API .check() 上的语法糖。你可以使用此 API 在单个细化中创建多个问题,或者完全控制生成的问题对象。

¥The .refine() API is syntactic sugar atop a more versatile (and verbose) API called .check(). You can use this API to create multiple issues in a single refinement or have full control of the generated issue objects.

const UniqueStringArray = z.array(z.string()).check((ctx) => {
  if (ctx.value.length > 3) {
    // full control of issue objects
    ctx.issues.push({
      code: "too_big",
      maximum: 3,
      origin: "array",
      inclusive: true,
      message: "Too many items 😡",
      input: ctx.value
    });
  }
 
  // create multiple issues in one refinement
  if (ctx.value.length !== new Set(ctx.value).size) {
    ctx.issues.push({
      code: "custom",
      message: `No duplicates allowed.`,
      input: ctx.value,
      continue: true // make this issue continuable (default: false)
    });
  }
});

常规 .refine API 仅会生成带有 "custom" 错误代码的问题,但 .check() 可以抛出其他类型的问题。有关 Zod 内部问题类型的更多信息,请阅读 错误自定义 文档。

¥The regular .refine API only generates issues with a "custom" error code, but .check() makes it possible to throw other issue types. For more information on Zod's internal issue types, read the Error customization docs.

管道

¥Pipes

Schema 可以链接在一起形成 "pipes"。管道主要在与 转换 结合使用时有用。

¥Schemas can be chained together into "pipes". Pipes are primarily useful when used in conjunction with Transforms.

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

转换

¥Transforms

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

¥Transforms are a special kind of schema. Instead of validating input, they accept anything and perform some transformation on the data. To define a transform:

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

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

¥To perform validation logic inside a transform, use ctx. To report a validation issue, push a new issue onto ctx.issues (similar to the .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;
  }
});

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

¥Most commonly, transforms are used in conjunction with Pipes. This combination is useful for performing some initial validation, then transforming the parsed data into another form.

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

.transform()

将某些模式通过管道传输到转换中是一种常见的模式,因此 Zod 提供了一种便捷的 .transform() 方法。

¥Piping some schema into a transform is a common pattern, so Zod provides a convenience .transform() method.

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

转换也可以是异步的:

¥Transforms can also be async:

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

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

¥If you use async transforms, you must use a .parseAsync or .safeParseAsync when parsing data! Otherwise Zod will throw an error.

.preprocess()

将转换通过管道传输到另一个模式是另一种常见模式,因此 Zod 提供了一个便捷的 z.preprocess() 函数。

¥Piping a transform into another schema is another common pattern, so Zod provides a convenience z.preprocess() function.

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

默认值

¥Defaults

要设置模式的默认值:

¥To set a default value for a schema:

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

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

¥Alternatively, you can pass a function which will be re-executed whenever a default value needs to be generated:

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,则立即返回默认值。因此,默认值必须可以赋值给 Schema 的输出类型。

¥In Zod, setting a default value will short-circuit the parsing process. If the input is undefined, the default value is eagerly returned. As such, the default value must be assignable to the output type of the schema.

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

有时,定义一个预故障 ("预解析默认值") 值很有用。如果输入是 undefined,则将解析预设值。解析过程没有短路。因此,默认值必须可以赋值给 Schema 的输入类型。

¥Sometimes, it's useful to define a prefault ("pre-parse default") value. If the input is undefined, the prefault value will be parsed instead. The parsing process is not short circuited. As such, the prefault value must be assignable to the input type of the schema.

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

如果你想通过一些可变的改进传递一些输入值,这也很有用。

¥This is also useful if you want to pass some input value through some mutating 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() 定义在发生验证错误时返回的后备值:

¥Use .catch() to define a fallback value to be returned in the event of a validation error:

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

或者,你可以传递一个函数,每当需要生成捕获值时,该函数都会重新执行。

¥Alternatively, you can pass a function which will be re-executed whenever a catch value needs to be generated.

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 的类型系统是 structural,这意味着两个结构等效的类型被视为相同。

¥TypeScript's type system is structural, meaning that two types that are structurally equivalent are considered the same.

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

在某些情况下,可能需要在 TypeScript 中模拟 名义类型。这可以通过品牌类型(也称为 "不透明类型")实现。

¥In some cases, it can be desirable to simulate nominal typing inside TypeScript. This can be achieved with branded types (also known as "opaque 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

在底层,这是通过将 "brand" 附加到架构的推断类型来实现的。

¥Under the hood, this works by attaching a "brand" to the schema's inferred type.

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

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

¥With this brand, any plain (unbranded) data structures are no longer assignable to the inferred type. You have to parse some data with the schema to get branded data.

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

¥Note that branded types do not affect the runtime result of .parse. It is a static-only construct.

只读

¥Readonly

要将模式标记为只读:

¥To mark a schema as readonly:

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

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

¥The inferred type of the new schemas will be marked as readonly. Note that in TypeScript, this only affects objects, arrays, tuples, Set, and Map:

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() 进行冻结,以防止修改。

¥Inputs will be parsed like normal, then the result will be frozen with Object.freeze() to prevent modifications.

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

JSON

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

¥To validate any JSON-encodable value:

const jsonSchema = z.json();

这是一个便捷的 API,它返回以下联合模式:

¥This is a convenience API that returns the following union 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)
  ]);
});

自定义

¥Custom

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

¥You can create a Zod schema for any TypeScript type by using z.custom(). This is useful for creating schemas for types that are not supported by Zod out of the box, such as template string literals.

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 将允许任何值。这可能很危险!

¥If you don't provide a validation function, Zod will allow any value. This can be dangerous!

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

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

¥You can customize the error message and other options by passing a second argument. This parameter works the same way as the params parameter of .refine.

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

函数

¥Functions

在 Zod 4 中,z.function() 不再返回 Zod 模式。

¥In Zod 4, z.function() no longer returns a Zod schema.

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

¥Zod provides a z.function() utility for defining Zod-validated functions. This way, you can avoid intermixing validation code with your business logic.

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

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

¥Function schemas have an .implement() method which accepts a function and returns a new function that automatically validates its inputs and outputs.

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

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

¥This function will throw a ZodError if the input is invalid:

computeTrimmedLength(42); // throws ZodError

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

¥If you only care about validating inputs, you can omit the output field.

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