Skip to main content

Zod 中文网

简介

¥Introduction

Zod 是一个 TypeScript 优先的模式声明和验证库。我使用术语 "schema" 来广泛指代任何数据类型,从简单的 string 到复杂的嵌套对象。

¥Zod is a TypeScript-first schema declaration and validation library. I'm using the term "schema" to broadly refer to any data type, from a simple string to a complex nested object.

Zod 的设计尽可能对开发者友好。目标是消除重复的类型声明。使用 Zod,你只需声明一次验证器,Zod 就会自动推断静态 TypeScript 类型。将更简单的类型组合成复杂的数据结构很容易。

¥Zod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures.

其他一些很棒的方面:

¥Some other great aspects:

  • 零依赖

    ¥Zero dependencies

  • 适用于 Node.js 和所有现代浏览器

    ¥Works in Node.js and all modern browsers

  • Tiny:8kb 最小化 + 压缩

    ¥Tiny: 8kb minified + zipped

  • 不可变:方法(例如 .optional())返回一个新实例

    ¥Immutable: methods (e.g. .optional()) return a new instance

  • 简洁、可链式的界面

    ¥Concise, chainable interface

  • 功能方法:解析,不验证

    ¥Functional approach: parse, don't validate

  • 也适用于纯 JavaScript!你不需要使用 TypeScript。

    ¥Works with plain JavaScript too! You don't need to use TypeScript.

生态系统

¥Ecosystem

越来越多的工具建立在 Zod 之上或原生支持 Zod!如果你已经在 Zod 之上构建了一个工具或库,请告诉我有关它的 在 Twitter 上开始讨论。我会在下面添加它并发推文。

¥There are a growing number of tools that are built atop or support Zod natively! If you've built a tool or library on top of Zod, tell me about it on Twitter or start a Discussion. I'll add it below and tweet it out.

资源

¥Resources

API 库

¥API libraries

  • tRPC:无需 GraphQL 即可构建端到端类型安全 API。

    ¥tRPC: Build end-to-end typesafe APIs without GraphQL.

  • @anatine/zod-nestjs:在 NestJS 项目中使用 Zod 的辅助方法。

    ¥@anatine/zod-nestjs: Helper methods for using Zod in a NestJS project.

  • zod-endpoints:使用 Zod 的契约优先严格类型端点。兼容 OpenAPI。

    ¥zod-endpoints: Contract-first strictly typed endpoints with Zod. OpenAPI compatible.

  • zhttp:一个与 OpenAPI 兼容的、严格类型的 http 库,具有 Zod 输入和响应验证。

    ¥zhttp: An OpenAPI compatible, strictly typed http library with Zod input and response validation.

  • domain-functions:使用可组合函数将你的业务逻辑与框架分离。使用 Zod 模式从头到尾进行一流的类型推断。

    ¥domain-functions: Decouple your business logic from your framework using composable functions. With first-class type inference from end to end powered by Zod schemas.

  • @zodios/core:具有由 axios 和 zod 支持的运行时和编译时验证的 typescript API 客户端。

    ¥@zodios/core: A typescript API client with runtime and compile time validation backed by axios and zod.

  • express-zod-api:使用 I/O 模式验证和自定义中间件构建基于 Express 的 API。

    ¥express-zod-api: Build Express-based APIs with I/O schema validation and custom middlewares.

  • tapiduck:使用 Zod 和 Express 实现端到端类型安全 JSON API;有点像 tRPC,但更简单。

    ¥tapiduck: End-to-end typesafe JSON APIs with Zod and Express; a bit like tRPC, but simpler.

  • koa-zod-router:使用 Zod 在 Koa 中创建具有 I/O 验证的类型安全路由。

    ¥koa-zod-router: Create typesafe routes in Koa with I/O validation using Zod.

  • zod-sockets:Zod 驱动的 Socket.IO 微框架,具有 I/O 验证和内置 AsyncAPI 规范

    ¥zod-sockets: Zod-powered Socket.IO microframework with I/O validation and built-in AsyncAPI specs

表单集成

¥Form integrations

  • react-hook-form:React Hook Form 的第一方 Zod 解析器。

    ¥react-hook-form: A first-party Zod resolver for React Hook Form.

  • zod-validation-error:从 ZodError 生成用户友好的错误消息。

    ¥zod-validation-error: Generate user-friendly error messages from ZodErrors.

  • zod-formik-adapter:社区维护的 Zod Formik 适配器。

    ¥zod-formik-adapter: A community-maintained Formik adapter for Zod.

  • react-zorm:使用 Zod 为 React 生成和验证独立 <form>

    ¥react-zorm: Standalone <form> generation and validation for React using Zod.

  • zodix:Remix 加载器和操作中的 FormData 和 URLSearchParams 的 Zod 实用程序。

    ¥zodix: Zod utilities for FormData and URLSearchParams in Remix loaders and actions.

  • conform:用于逐步增强 HTML 表单的类型安全表单验证库。适用于 Remix 和 Next.js。

    ¥conform: A typesafe form validation library for progressive enhancement of HTML forms. Works with Remix and Next.js.

  • remix-params-helper:简化 Zod 与 Remix 应用的标准 URLSearchParams 和 FormData 的集成。

    ¥remix-params-helper: Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps.

  • formik-validator-zod:兼容 Formik 的验证器库,可简化 Zod 与 Formik 的使用。

    ¥formik-validator-zod: Formik-compliant validator library that simplifies using Zod with Formik.

  • zod-i18n-map:用于翻译 Zod 错误消息。

    ¥zod-i18n-map: Useful for translating Zod error messages.

  • @modular-forms/solid:支持 Zod 验证的 SolidJS 模块化表单库。

    ¥@modular-forms/solid: Modular form library for SolidJS that supports Zod for validation.

  • houseform:使用 Zod 进行验证的 React 表单库。

    ¥houseform: A React form library that uses Zod for validation.

  • sveltekit-superforms:带有 Zod 验证的 SvelteKit 增强型表单库。

    ¥sveltekit-superforms: Supercharged form library for SvelteKit with Zod validation.

  • mobx-zod-form:基于 MobX 和 Zod 的数据优先表单构建器。

    ¥mobx-zod-form: Data-first form builder based on MobX & Zod.

  • @vee-validate/zod:带有 Zod 模式验证的 Vue.js 表单库。

    ¥@vee-validate/zod: Form library for Vue.js with Zod schema validation.

Zod 到 X

¥Zod to X

X 到 Zod

¥X to Zod

模拟

¥Mocking

由 Zod 提供支持

¥Powered by Zod

  • freerstore:Firestore 成本优化器。

    ¥freerstore: Firestore cost optimizer.

  • slonik:具有强大 Zod 集成的 Node.js Postgres 客户端。

    ¥slonik: Node.js Postgres client with strong Zod integration.

  • soly:使用 zod 创建 CLI 应用。

    ¥soly: Create CLI applications with zod.

  • pastel:使用 react、zod 和 ink 创建 CLI 应用。

    ¥pastel: Create CLI applications with react, zod, and ink.

  • zod-xlsx:使用 Zod 模式的基于 xlsx 的资源验证器。

    ¥zod-xlsx: A xlsx based resource validator using Zod schemas.

  • znv:使用 Zod 模式对 Node.js 进行类型安全环境解析和验证。

    ¥znv: Type-safe environment parsing and validation for Node.js with Zod schemas.

  • zod-config:使用灵活的适配器跨多个源加载配置,确保 Zod 的类型安全。

    ¥zod-config: Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod.

Zod 实用程序

¥Utilities for Zod

安装

¥Installation

要求

¥Requirements

  • TypeScript 4.5+!

  • 你必须在 tsconfig.json 中启用 strict 模式。这是所有 TypeScript 项目的最佳实践。

    ¥You must enable strict mode in your tsconfig.json. This is a best practice for all TypeScript projects.

    // tsconfig.json
    {
    // ...
    "compilerOptions": {
    // ...
    "strict": true
    }
    }

来自 npm (Node/Bun)

¥From npm (Node/Bun)

npm install zod       # npm
yarn add zod # yarn
bun add zod # bun
pnpm add zod # pnpm

Zod 还在每次提交时发布一个预览版本。要安装预览版:

¥Zod also publishes a canary version on every commit. To install the canary:

npm install zod@canary       # npm
yarn add zod@canary # yarn
bun add zod@canary # bun
pnpm add zod@canary # pnpm

来自 deno.land/x (Deno)

¥From deno.land/x (Deno)

与 Node 不同,Deno 依赖于直接 URL 导入,而不是像 NPM 这样的包管理器。Zod 可在 deno.land/x 上使用。最新版本可以像这样导入:

¥Unlike Node, Deno relies on direct URL imports instead of a package manager like NPM. Zod is available on deno.land/x. The latest version can be imported like so:

import { z } from "https://deno.land/x/zod/mod.ts";

你还可以指定特定版本:

¥You can also specify a particular version:

import { z } from "https://deno.land/x/zod@v3.16.1/mod.ts";

本 README 的其余部分假定你正在使用 npm 并直接从 "zod" 包导入。

¥The rest of this README assumes you are using npm and importing directly from the "zod" package.

基本用法

¥Basic usage

创建一个简单的字符串模式

¥Creating a simple string schema

import { z } from "zod";

// creating a schema for strings
const mySchema = z.string();

// parsing
mySchema.parse("tuna"); // => "tuna"
mySchema.parse(12); // => throws ZodError

// "safe" parsing (doesn't throw error if validation fails)
mySchema.safeParse("tuna"); // => { success: true; data: "tuna" }
mySchema.safeParse(12); // => { success: false; error: ZodError }

创建对象模式

¥Creating an object schema

import { z } from "zod";

const User = z.object({
username: z.string(),
});

User.parse({ username: "Ludwig" });

// extract the inferred type
type User = z.infer<typeof User>;
// { username: string }

原始

¥Primitives

import { z } from "zod";

// primitive values
z.string();
z.number();
z.bigint();
z.boolean();
z.date();
z.symbol();

// empty types
z.undefined();
z.null();
z.void(); // accepts undefined

// catch-all types
// allows any value
z.any();
z.unknown();

// never type
// allows no values
z.never();

原语强制

¥Coercion for primitives

Zod 现在提供了一种更方便的方式来强制原始值。

¥Zod now provides a more convenient way to coerce primitive values.

const schema = z.coerce.string();
schema.parse("tuna"); // => "tuna"
schema.parse(12); // => "12"

在解析步骤中,输入通过 String() 函数传递,这是一个内置的 JavaScript,用于将数据强制转换为字符串。

¥During the parsing step, the input is passed through the String() function, which is a JavaScript built-in for coercing data into strings.

schema.parse(12); // => "12"
schema.parse(true); // => "true"
schema.parse(undefined); // => "undefined"
schema.parse(null); // => "null"

返回的模式是一个普通的 ZodString 实例,因此你可以使用所有字符串方法。

¥The returned schema is a normal ZodString instance so you can use all string methods.

z.coerce.string().email().min(5);

强制如何工作

¥How coercion works

所有原始类型都支持强制转换。Zod 使用内置构造函数强制所有输入:String(input)Number(input)new Date(input) 等。

¥All primitive types support coercion. Zod coerces all inputs using the built-in constructors: String(input), Number(input), new Date(input), etc.

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

注意 — z.coerce.boolean() 的布尔强制转换可能无法按预期工作。任何 truthy 值都强制转换为 true,任何 falsy 值都强制转换为 false

¥Note — Boolean coercion with z.coerce.boolean() may not work how you expect. Any truthy value is coerced to true, and any falsy value is coerced to false.

const schema = z.coerce.boolean(); // Boolean(input)

schema.parse("tuna"); // => true
schema.parse("true"); // => true
schema.parse("false"); // => true
schema.parse(1); // => true
schema.parse([]); // => true

schema.parse(0); // => false
schema.parse(""); // => false
schema.parse(undefined); // => false
schema.parse(null); // => false

为了更好地控制强制逻辑,请考虑使用 z.preprocessz.pipe()

¥For more control over coercion logic, consider using z.preprocess or z.pipe().

字面量

¥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); // bigint literal
const tru = z.literal(true);

const terrificSymbol = Symbol("terrific");
const terrific = z.literal(terrificSymbol);

// retrieve literal value
tuna.value; // "tuna"

目前 Zod 不支持日期字面量。如果你有此功能的用例,请提交问题。

¥Currently there is no support for Date literals in Zod. If you have a use case for this feature, please file an issue.

字符串

¥Strings

Zod 包含一些字符串特定的验证。

¥Zod includes a handful of string-specific validations.

// validations
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().email();
z.string().url();
z.string().emoji();
z.string().uuid();
z.string().nanoid();
z.string().cuid();
z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().datetime(); // ISO 8601; by default only `Z` timezone allowed
z.string().ip(); // defaults to allow both IPv4 and IPv6

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

// added in Zod 3.23
z.string().date(); // ISO date format (YYYY-MM-DD)
z.string().time(); // ISO time format (HH:mm:ss[.SSSSSS])
z.string().duration(); // ISO 8601 duration
z.string().base64();

查看 validator.js 以获取可与 改进 结合使用的许多其他有用的字符串验证函数。

¥Check out validator.js for a bunch of other useful string validation functions that can be used in conjunction with Refinements.

你可以在创建字符串模式时自定义一些常见错误消息。

¥You can customize some common error messages when creating a string schema.

const name = z.string({
required_error: "Name is required",
invalid_type_error: "Name must be a string",
});

使用验证方法时,可以传入附加参数以提供自定义错误消息。

¥When using validation methods, you can pass in an additional argument to provide a custom error message.

z.string().min(5, { message: "Must be 5 or more characters long" });
z.string().max(5, { message: "Must be 5 or fewer characters long" });
z.string().length(5, { message: "Must be exactly 5 characters long" });
z.string().email({ message: "Invalid email address" });
z.string().url({ message: "Invalid url" });
z.string().emoji({ message: "Contains non-emoji characters" });
z.string().uuid({ message: "Invalid UUID" });
z.string().includes("tuna", { message: "Must include tuna" });
z.string().startsWith("https://", { message: "Must provide secure URL" });
z.string().endsWith(".com", { message: "Only .com domains allowed" });
z.string().datetime({ message: "Invalid datetime string! Must be UTC." });
z.string().date({ message: "Invalid date string!" });
z.string().time({ message: "Invalid time string!" });
z.string().ip({ message: "Invalid IP address" });

日期时间

¥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.string().datetime() 方法强制执行 ISO 8601;默认没有时区偏移和任意亚秒小数精度。

¥The z.string().datetime() method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision.

const datetime = z.string().datetime();

datetime.parse("2020-01-01T00:00:00Z"); // pass
datetime.parse("2020-01-01T00:00:00.123Z"); // pass
datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision)
datetime.parse("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed)

可以通过将 offset 选项设置为 true 来允许时区偏移。

¥Timezone offsets can be allowed by setting the offset option to true.

const datetime = z.string().datetime({ offset: true });

datetime.parse("2020-01-01T00:00:00+02:00"); // pass
datetime.parse("2020-01-01T00:00:00.123+02:00"); // pass (millis optional)
datetime.parse("2020-01-01T00:00:00.123+0200"); // pass (millis optional)
datetime.parse("2020-01-01T00:00:00.123+02"); // pass (only offset hours)
datetime.parse("2020-01-01T00:00:00Z"); // pass (Z still supported)

你还可以限制允许的 precision。默认情况下,支持任意亚秒精度(但可选)。

¥You can additionally constrain the allowable precision. By default, arbitrary sub-second precision is supported (but optional).

const datetime = z.string().datetime({ precision: 3 });

datetime.parse("2020-01-01T00:00:00.123Z"); // pass
datetime.parse("2020-01-01T00:00:00Z"); // fail
datetime.parse("2020-01-01T00:00:00.123456Z"); // fail

日期

¥Dates

Zod 3.23 中添加

¥Added in Zod 3.23

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

¥The z.string().date() method validates strings in the format YYYY-MM-DD.

const date = z.string().date();

date.parse("2020-01-01"); // pass
date.parse("2020-1-1"); // fail
date.parse("2020-01-32"); // fail

时间

¥Times

Zod 3.23 中添加

¥Added in Zod 3.23

z.string().time() 方法验证 HH:MM:SS[.s+] 格式的字符串。第二个可以包含任意十进制精度。它不允许任何类型的时区偏移。

¥The z.string().time() method validates strings in the format HH:MM:SS[.s+]. The second can include arbitrary decimal precision. It does not allow timezone offsets of any kind.

const time = z.string().time();

time.parse("00:00:00"); // pass
time.parse("09:52:31"); // pass
time.parse("23:59:59.9999999"); // pass (arbitrary precision)

time.parse("00:00:00.123Z"); // fail (no `Z` allowed)
time.parse("00:00:00.123+02:00"); // fail (no offsets allowed)

你可以设置 precision 选项来限制允许的小数精度。

¥You can set the precision option to constrain the allowable decimal precision.

const time = z.string().time({ precision: 3 });

time.parse("00:00:00.123"); // pass
time.parse("00:00:00.123456"); // fail
time.parse("00:00:00"); // fail

IP 地址

¥IP addresses

z.string().ip() 方法默认验证 IPv4 和 IPv6。

¥The z.string().ip() method by default validate IPv4 and IPv6.

const ip = z.string().ip();

ip.parse("192.168.1.1"); // pass
ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // pass
ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // pass

ip.parse("256.1.1.1"); // fail
ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // fail

你还可以设置 IP version

¥You can additionally set the IP version.

const ipv4 = z.string().ip({ version: "v4" });
ipv4.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail

const ipv6 = z.string().ip({ version: "v6" });
ipv6.parse("192.168.1.1"); // fail

数字

¥Numbers

你可以在创建数字模式时自定义某些错误消息。

¥You can customize certain error messages when creating a number schema.

const age = z.number({
required_error: "Age is required",
invalid_type_error: "Age must be a number",
});

Zod 包含一些数字特定的验证。

¥Zod includes 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().int(); // value must be an integer

z.number().positive(); // > 0
z.number().nonnegative(); // >= 0
z.number().negative(); // < 0
z.number().nonpositive(); // <= 0

z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5)

z.number().finite(); // value must be finite, not Infinity or -Infinity
z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER

或者,你可以传入第二个参数来提供自定义错误消息。

¥Optionally, you can pass in a second argument to provide a custom error message.

z.number().lte(5, { message: "this👏is👏too👏big" });

BigInts

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(); // > 0n
z.bigint().nonnegative(); // >= 0n
z.bigint().negative(); // < 0n
z.bigint().nonpositive(); // <= 0n

z.bigint().multipleOf(5n); // Evenly divisible by 5n.

NaNs

你可以在创建 nan 模式时自定义某些错误消息。

¥You can customize certain error messages when creating a nan schema.

const isNaN = z.nan({
required_error: "isNaN is required",
invalid_type_error: "isNaN must be 'not a number'",
});

布尔值

¥Booleans

你可以在创建布尔模式时自定义某些错误消息。

¥You can customize certain error messages when creating a boolean schema.

const isActive = z.boolean({
required_error: "isActive is required",
invalid_type_error: "isActive must be a boolean",
});

日期

¥Dates

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

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

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

你可以在创建日期模式时自定义某些错误消息。

¥You can customize certain error messages when creating a date schema.

const myDateSchema = z.date({
required_error: "Please select a date and time",
invalid_type_error: "That's not a date!",
});

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

¥Zod provides a handful of date-specific validations.

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

强制转换为日期

¥Coercion to Date

zod 3.20 起,使用 z.coerce.date() 将输入传递到 new Date(input)

¥Since zod 3.20, use z.coerce.date() to pass the input through new Date(input).

const dateSchema = z.coerce.date();
type DateSchema = z.infer<typeof dateSchema>;
// type DateSchema = Date

/* valid dates */
console.log(dateSchema.safeParse("2023-01-10T00:00:00.000Z").success); // true
console.log(dateSchema.safeParse("2023-01-10").success); // true
console.log(dateSchema.safeParse("1/10/23").success); // true
console.log(dateSchema.safeParse(new Date("1/10/23")).success); // true

/* invalid dates */
console.log(dateSchema.safeParse("2023-13-10").success); // false
console.log(dateSchema.safeParse("0000-00-00").success); // false

对于较旧的 zod 版本,使用 z.preprocess 就像 在此线程中描述 一样。

¥For older zod versions, use z.preprocess like described in this thread.

Zod 枚举

¥Zod enums

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

z.enum 是一种 Zod 原生方式,用于声明具有一组固定允许字符串值的模式。将值数组直接传递到 z.enum()。或者,使用 as const 将枚举值定义为字符串元组。有关详细信息,请参阅 const 断言文档

¥z.enum is a Zod-native way to declare a schema with a fixed set of allowable string values. Pass the array of values directly into z.enum(). Alternatively, use as const to define your enum values as a tuple of strings. See the const assertion docs for details.

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

这是不允许的,因为 Zod 无法推断每个元素的确切值。

¥This is not allowed, since Zod isn't able to infer the exact values of each element.

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

.enum

要使用 Zod 枚举实现自动补齐,请使用模式的 .enum 属性:

¥To get autocompletion with a Zod enum, use the .enum property of your schema:

FishEnum.enum.Salmon; // => autocompletes

FishEnum.enum;
/*
=> {
Salmon: "Salmon",
Tuna: "Tuna",
Trout: "Trout",
}
*/

你还可以使用 .options 属性以元组形式检索选项列表:

¥You can also retrieve the list of options as a tuple with the .options property:

FishEnum.options; // ["Salmon", "Tuna", "Trout"];

.exclude/.extract()

你可以使用 .exclude.extract 方法创建 Zod 枚举的子集。

¥You can create subsets of a Zod enum with the .exclude and .extract methods.

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

原生枚举

¥Native enums

Zod 枚举是定义和验证枚举的推荐方法。但如果你需要针对第三方库中的枚举进行验证(或者你不想重写现有的枚举),则可以使用 z.nativeEnum()

¥Zod enums are the recommended approach to defining and validating enums. But if you need to validate against an enum from a third-party library (or you don't want to rewrite your existing enums) you can use z.nativeEnum().

数字枚举

¥Numeric enums

enum Fruits {
Apple,
Banana,
}

const FruitEnum = z.nativeEnum(Fruits);
type FruitEnum = z.infer<typeof FruitEnum>; // Fruits

FruitEnum.parse(Fruits.Apple); // passes
FruitEnum.parse(Fruits.Banana); // passes
FruitEnum.parse(0); // passes
FruitEnum.parse(1); // passes
FruitEnum.parse(3); // fails

字符串枚举

¥String enums

enum Fruits {
Apple = "apple",
Banana = "banana",
Cantaloupe, // you can mix numerical and string enums
}

const FruitEnum = z.nativeEnum(Fruits);
type FruitEnum = z.infer<typeof FruitEnum>; // Fruits

FruitEnum.parse(Fruits.Apple); // passes
FruitEnum.parse(Fruits.Cantaloupe); // passes
FruitEnum.parse("apple"); // passes
FruitEnum.parse("banana"); // passes
FruitEnum.parse(0); // passes
FruitEnum.parse("Cantaloupe"); // fails

Const 枚举

¥Const enums

.nativeEnum() 函数也适用于 as const 对象。⚠️ as const 需要 TypeScript 3.4+!

¥The .nativeEnum() function works for as const objects as well. ⚠️ as const requires TypeScript 3.4+!

const Fruits = {
Apple: "apple",
Banana: "banana",
Cantaloupe: 3,
} as const;

const FruitEnum = z.nativeEnum(Fruits);
type FruitEnum = z.infer<typeof FruitEnum>; // "apple" | "banana" | 3

FruitEnum.parse("apple"); // passes
FruitEnum.parse("banana"); // passes
FruitEnum.parse(3); // passes
FruitEnum.parse("Cantaloupe"); // fails

你可以使用 .enum 属性访问底层对象:

¥You can access the underlying object with the .enum property:

FruitEnum.enum.Apple; // "apple"

可选项

¥Optionals

你可以使用 z.optional() 使任何模式成为可选的。这将模式封装在 ZodOptional 实例中并返回结果。

¥You can make any schema optional with z.optional(). This wraps the schema in a ZodOptional instance and returns the result.

const schema = z.optional(z.string());

schema.parse(undefined); // => returns undefined
type A = z.infer<typeof schema>; // string | undefined

为方便起见,你还可以在现有模式上调用 .optional() 方法。

¥For convenience, you can also call the .optional() method on an existing schema.

const user = z.object({
username: z.string().optional(),
});
type C = z.infer<typeof user>; // { username?: string | undefined };

你可以使用 .unwrap()ZodOptional 实例中提取封装的模式。

¥You can extract the wrapped schema from a ZodOptional instance with .unwrap().

const stringSchema = z.string();
const optionalString = stringSchema.optional();
optionalString.unwrap() === stringSchema; // true

可空值

¥Nullables

类似地,你可以使用 z.nullable() 创建可空类型。

¥Similarly, you can create nullable types with z.nullable().

const nullableString = z.nullable(z.string());
nullableString.parse("asdf"); // => "asdf"
nullableString.parse(null); // => null

或者使用 .nullable() 方法。

¥Or use the .nullable() method.

const E = z.string().nullable(); // equivalent to nullableString
type E = z.infer<typeof E>; // string | null

使用 .unwrap() 提取内部模式。

¥Extract the inner schema with .unwrap().

const stringSchema = z.string();
const nullableString = stringSchema.nullable();
nullableString.unwrap() === stringSchema; // true

对象

¥Objects

// all properties are required by default
const Dog = z.object({
name: z.string(),
age: z.number(),
});

// extract the inferred type like this
type Dog = z.infer<typeof Dog>;

// equivalent to:
type Dog = {
name: string;
age: number;
};

.shape

使用 .shape 访问特定键的模式。

¥Use .shape to access the schemas for a particular key.

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

.keyof

使用 .keyof 从对象模式的键创建 ZodEnum 模式。

¥Use .keyof to create a ZodEnum schema from the keys of an object schema.

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

.extend

你可以使用 .extend 方法向对象模式添加其他字段。

¥You can add additional fields to an object schema with the .extend method.

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

你可以使用 .extend 覆盖字段!小心使用这种力量!

¥You can use .extend to overwrite fields! Be careful with this power!

.merge

相当于 A.extend(B.shape)

¥Equivalent to A.extend(B.shape).

const BaseTeacher = z.object({ students: z.array(z.string()) });
const HasID = z.object({ id: z.string() });

const Teacher = BaseTeacher.merge(HasID);
type Teacher = z.infer<typeof Teacher>; // => { students: string[], id: string }

如果两个模式共享密钥,则 B 的属性将覆盖 A 的属性。返回的模式还继承了 "unknownKeys" 策略(strip/strict/passthrough)和 B 的 catchall 模式。

¥If the two schemas share keys, the properties of B overrides the property of A. The returned schema also inherits the "unknownKeys" policy (strip/strict/passthrough) and the catchall schema of B.

.pick/.omit

受 TypeScript 内置 PickOmit 工具类型的启发,所有 Zod 对象模式都有返回修改版本的 .pick.omit 方法。考虑此 Recipe 模式:

¥Inspired by TypeScript's built-in Pick and Omit utility types, all Zod object schemas have .pick and .omit methods that return a modified version. Consider this Recipe schema:

const Recipe = z.object({
id: z.string(),
name: z.string(),
ingredients: z.array(z.string()),
});

要仅保留某些键,请使用 .pick

¥To only keep certain keys, use .pick .

const JustTheName = Recipe.pick({ name: true });
type JustTheName = z.infer<typeof JustTheName>;
// => { name: string }

要删除某些键,请使用 .omit

¥To remove certain keys, use .omit .

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

type NoIDRecipe = z.infer<typeof NoIDRecipe>;
// => { name: string, ingredients: string[] }

.partial

受内置 TypeScript 工具类型 部分 的启发,.partial 方法使所有属性都成为可选的。

¥Inspired by the built-in TypeScript utility type Partial, the .partial method makes all properties optional.

从这个对象开始:

¥Starting from this object:

const user = z.object({
email: z.string(),
username: z.string(),
});
// { email: string; username: string }

我们可以创建部分版本:

¥We can create a partial version:

const partialUser = user.partial();
// { email?: string | undefined; username?: string | undefined }

你还可以指定使哪些属性成为可选:

¥You can also specify which properties to make optional:

const optionalEmail = user.partial({
email: true,
});
/*
{
email?: string | undefined;
username: string
}
*/

.deepPartial

.partial 方法很浅 — 它只应用一个深度级别。还有一个 "deep" 版本:

¥The .partial method is shallow — it only applies one level deep. There is also a "deep" version:

const user = z.object({
username: z.string(),
location: z.object({
latitude: z.number(),
longitude: z.number(),
}),
strings: z.array(z.object({ value: z.string() })),
});

const deepPartialUser = user.deepPartial();

/*
{
username?: string | undefined,
location?: {
latitude?: number | undefined;
longitude?: number | undefined;
} | undefined,
strings?: { value?: string}[]
}
*/

重要限制:深层部分仅在对象、数组和元组的层次结构中按预期工作。

¥Important limitation: deep partials only work as expected in hierarchies of objects, arrays, and tuples.

.required

.partial 方法相反,.required 方法使所有属性成为必需。

¥Contrary to the .partial method, the .required method makes all properties required.

从这个对象开始:

¥Starting from this object:

const user = z
.object({
email: z.string(),
username: z.string(),
})
.partial();
// { email?: string | undefined; username?: string | undefined }

我们可以创建必需版本:

¥We can create a required version:

const requiredUser = user.required();
// { email: string; username: string }

你还可以指定哪些属性是必需的:

¥You can also specify which properties to make required:

const requiredEmail = user.required({
email: true,
});
/*
{
email: string;
username?: string | undefined;
}
*/

.passthrough

默认情况下,Zod 对象模式会在解析过程中删除无法识别的键。

¥By default Zod object schemas strip out unrecognized keys during parsing.

const person = z.object({
name: z.string(),
});

person.parse({
name: "bob dylan",
extraKey: 61,
});
// => { name: "bob dylan" }
// extraKey has been stripped

相反,如果你想传递未知键,请使用 .passthrough()

¥Instead, if you want to pass through unknown keys, use .passthrough() .

person.passthrough().parse({
name: "bob dylan",
extraKey: 61,
});
// => { name: "bob dylan", extraKey: 61 }

.strict

默认情况下,Zod 对象模式会在解析过程中删除无法识别的键。你可以使用 .strict() 禁止未知键。如果输入中有任何未知键,Zod 将抛出错误。

¥By default Zod object schemas strip out unrecognized keys during parsing. You can disallow unknown keys with .strict() . If there are any unknown keys in the input, Zod will throw an error.

const person = z
.object({
name: z.string(),
})
.strict();

person.parse({
name: "bob dylan",
extraKey: 61,
});
// => throws ZodError

.strip

你可以使用 .strip 方法将对象模式重置为默认行为(剥离无法识别的键)。

¥You can use the .strip method to reset an object schema to the default behavior (stripping unrecognized keys).

.catchall

你可以将 "catchall" 模式传递到对象模式中。所有未知键都将根据它进行验证。

¥You can pass a "catchall" schema into an object schema. All unknown keys will be validated against it.

const person = z
.object({
name: z.string(),
})
.catchall(z.number());

person.parse({
name: "bob dylan",
validExtraKey: 61, // works fine
});

person.parse({
name: "bob dylan",
validExtraKey: false, // fails
});
// => throws ZodError

使用 .catchall() 可避免 .passthrough().strip().strict()。现在所有键都被视为 "known"。

¥Using .catchall() obviates .passthrough() , .strip() , or .strict(). All keys are now considered "known".

数组

¥Arrays

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

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

小心使用 .array() 方法。它返回一个新的 ZodArray 实例。这意味着调用方法的顺序很重要。例如:

¥Be careful with the .array() method. It returns a new ZodArray instance. This means the order in which you call methods matters. For instance:

z.string().optional().array(); // (string | undefined)[]
z.string().array().optional(); // string[] | undefined

.element

使用 .element 访问数组元素的模式。

¥Use .element to access the schema for an element of the array.

stringArray.element; // => string schema

.nonempty

如果你想确保数组至少包含一个元素,请使用 .nonempty()

¥If you want to ensure that an array contains at least one element, use .nonempty().

const nonEmptyStrings = z.string().array().nonempty();
// the inferred type is now
// [string, ...string[]]

nonEmptyStrings.parse([]); // throws: "Array cannot be empty"
nonEmptyStrings.parse(["Ariana Grande"]); // passes

你可以选择指定自定义错误消息:

¥You can optionally specify a custom error message:

// optional custom error message
const nonEmptyStrings = z.string().array().nonempty({
message: "Can't be empty!",
});

.min/.max/.length

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

.nonempty() 不同,这些方法不会更改推断的类型。

¥Unlike .nonempty() these methods do not change the inferred type.

元组

¥Tuples

与数组不同,元组具有固定数量的元素,并且每个元素可以具有不同的类型。

¥Unlike arrays, tuples have a fixed number of elements and each element can have a different type.

const athleteSchema = z.tuple([
z.string(), // name
z.number(), // jersey number
z.object({
pointsScored: z.number(),
}), // statistics
]);

type Athlete = z.infer<typeof athleteSchema>;
// type Athlete = [string, number, { pointsScored: number }]

可以使用 .rest 方法添加可变参数 ("rest") 参数。

¥A variadic ("rest") argument can be added with the .rest method.

const variadicTuple = z.tuple([z.string()]).rest(z.number());
const result = variadicTuple.parse(["hello", 1, 2, 3]);
// => [string, ...number[]];

联合

¥Unions

Zod 包含一个内置的 z.union 方法,用于组成 "OR" 类型。

¥Zod includes a built-in z.union method for composing "OR" types.

const stringOrNumber = z.union([z.string(), z.number()]);

stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes

Zod 将按顺序针对每个 "options" 测试输入,并返回第一个成功验证的值。

¥Zod will test the input against each of the "options" in order and return the first value that validates successfully.

为了方便起见,你还可以使用 .or 方法

¥For convenience, you can also use the .or method:

const stringOrNumber = z.string().or(z.number());

可选字符串验证:

¥Optional string validation:

要验证可选的表单输入,你可以将所需的字符串验证与空字符串 literal 联合。

¥To validate an optional form input, you can union the desired string validation with an empty string literal.

此示例验证可选的输入,但需要包含 有效 URL

¥This example validates an input that is optional but needs to contain a valid URL:

const optionalUrl = z.union([z.string().url().nullish(), z.literal("")]);

console.log(optionalUrl.safeParse(undefined).success); // true
console.log(optionalUrl.safeParse(null).success); // true
console.log(optionalUrl.safeParse("").success); // true
console.log(optionalUrl.safeParse("https://zod.dev").success); // true
console.log(optionalUrl.safeParse("not a valid url").success); // false

可区分联合

¥Discriminated unions

可区分联合是共享特定键的对象模式的联合。

¥A discriminated union is a union of object schemas that all share a particular key.

type MyUnion =
| { status: "success"; data: string }
| { status: "failed"; error: Error };

此类联合可以用 z.discriminatedUnion 方法表示。这可以实现更快的评估,因为 Zod 可以检查鉴别器键(上例中的 status)来确定应使用哪个模式来解析输入。这使得解析更有效率,并让 Zod 报告更友好的错误。

¥Such unions can be represented with the z.discriminatedUnion method. This enables faster evaluation, because Zod can check the discriminator key (status in the example above) to determine which schema should be used to parse the input. This makes parsing more efficient and lets Zod report friendlier errors.

使用基本联合方法,将针对每个提供的 "options" 测试输入,如果无效,所有 "options" 的问题都会显示在 zod 错误中。另一方面,可区分联合允许只选择其中一个 "options",对其进行测试,并仅显示与此 "option" 相关的问题。

¥With the basic union method, the input is tested against each of the provided "options", and in the case of invalidity, issues for all the "options" are shown in the zod error. On the other hand, the discriminated union allows for selecting just one of the "options", testing against it, and showing only the issues related to this "option".

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

myUnion.parse({ status: "success", data: "yippie ki yay" });

你可以使用 .options 属性提取对模式数组的引用。

¥You can extract a reference to the array of schemas with the .options property.

myUnion.options; // [ZodObject<...>, ZodObject<...>]

要合并两个或多个可区分联合,请使用带有解构的 .options

¥To merge two or more discriminated unions, use .options with destructuring.

const A = z.discriminatedUnion("status", [
/* options */
]);
const B = z.discriminatedUnion("status", [
/* options */
]);

const AB = z.discriminatedUnion("status", [...A.options, ...B.options]);

记录

¥Records

记录模式用于验证 Record<string, number> 等类型。这对于按 ID 存储或缓存项目特别有用。

¥Record schemas are used to validate types such as Record<string, number>. This is particularly useful for storing or caching items by ID.

const User = z.object({ name: z.string() });

const UserStore = z.record(z.string(), User);
type UserStore = z.infer<typeof UserStore>;
// => Record<string, { name: string }>

模式和推断类型可以像这样使用:

¥The schema and inferred type can be used like so:

const userStore: UserStore = {};

userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = {
name: "Carlotta",
}; // passes

userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = {
whatever: "Ice cream sundae",
}; // TypeError

关于数字键的说明

¥A note on numerical keys

虽然 z.record(keyType, valueType) 能够接受数字键类型,并且 TypeScript 的内置记录类型是 Record<KeyType, ValueType>,但很难在 Zod 中表示 TypeScript 类型 Record<number, any>

¥While z.record(keyType, valueType) is able to accept numerical key types and TypeScript's built-in Record type is Record<KeyType, ValueType>, it's hard to represent the TypeScript type Record<number, any> in Zod.

事实证明,TypeScript 围绕 [k: number] 的行为有点不直观:

¥As it turns out, TypeScript's behavior surrounding [k: number] is a little unintuitive:

const testMap: { [k: number]: string } = {
1: "one",
};

for (const key in testMap) {
console.log(`${key}: ${typeof key}`);
}
// prints: `1: string`

如你所见,JavaScript 会自动将所有对象键转换为后台字符串。由于 Zod 试图弥合静态类型和运行时类型之间的差距,因此提供一种使用数字键创建记录模式的方法毫无意义,因为运行时 JavaScript 中没有数字键。

¥As you can see, JavaScript automatically casts all object keys to strings under the hood. Since Zod is trying to bridge the gap between static and runtime types, it doesn't make sense to provide a way of creating a record schema with numerical keys, since there's no such thing as a numerical key in runtime JavaScript.

Map

const stringNumberMap = z.map(z.string(), z.number());

type StringNumberMap = z.infer<typeof stringNumberMap>;
// type StringNumberMap = Map<string, number>

集合

¥Sets

const numberSet = z.set(z.number());
type NumberSet = z.infer<typeof numberSet>;
// type NumberSet = Set<number>

可以使用以下实用方法进一步约束集合模式。

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

z.set(z.string()).nonempty(); // must contain at least one item
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

交叉点

¥Intersections

交集对于创建 "逻辑与" 类型很有用。这对于交叉两种对象类型很有用。

¥Intersections are useful for creating "logical AND" types. This is useful for intersecting two object types.

const Person = z.object({
name: z.string(),
});

const Employee = z.object({
role: z.string(),
});

const EmployedPerson = z.intersection(Person, Employee);

// equivalent to:
const EmployedPerson = Person.and(Employee);

虽然在许多情况下,建议使用 A.merge(B) 合并两个对象。.merge 方法返回一个新的 ZodObject 实例,而 A.and(B) 返回一个不太有用的 ZodIntersection 实例,它缺少像 pickomit 这样的通用对象方法。

¥Though in many cases, it is recommended to use A.merge(B) to merge two objects. The .merge method returns a new ZodObject instance, whereas A.and(B) returns a less useful ZodIntersection instance that lacks common object methods like pick and omit.

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

递归类型

¥Recursive types

你可以在 Zod 中定义递归模式,但由于 TypeScript 的限制,无法静态推断其类型。相反,你需要手动定义类型定义,并将其作为 "类型提示" 提供给 Zod。

¥You can define a recursive schema in Zod, but because of a limitation of TypeScript, their type can't be statically inferred. Instead you'll need to define the type definition manually, and provide it to Zod as a "type hint".

const baseCategorySchema = z.object({
name: z.string(),
});

type Category = z.infer<typeof baseCategorySchema> & {
subcategories: Category[];
};

const categorySchema: z.ZodType<Category> = baseCategorySchema.extend({
subcategories: z.lazy(() => categorySchema.array()),
});

categorySchema.parse({
name: "People",
subcategories: [
{
name: "Politicians",
subcategories: [
{
name: "Presidents",
subcategories: [],
},
],
},
],
}); // passes

感谢 crasite 提供此示例。

¥Thanks to crasite for this example.

ZodType 与 ZodEffects

¥ZodType with ZodEffects

z.ZodTypez.ZodEffects.refine.transformpreprocess 等)一起使用时,需要定义模式的输入和输出类型。z.ZodType<Output, z.ZodTypeDef, Input>

¥When using z.ZodType with z.ZodEffects ( .refine, .transform, preprocess, etc... ), you will need to define the input and output types of the schema. z.ZodType<Output, z.ZodTypeDef, Input>

const isValidId = (id: string): id is `${string}/${string}` =>
id.split("/").length === 2;

const baseSchema = z.object({
id: z.string().refine(isValidId),
});

type Input = z.input<typeof baseSchema> & {
children: Input[];
};

type Output = z.output<typeof baseSchema> & {
children: Output[];
};

const schema: z.ZodType<Output, z.ZodTypeDef, Input> = baseSchema.extend({
children: z.lazy(() => schema.array()),
});

感谢 marcus13371337JoelBeeldi 提供此示例。

¥Thanks to marcus13371337 and JoelBeeldi for this example.

JSON 类型

¥JSON type

如果你想验证任何 JSON 值,你可以使用下面的代码片段。

¥If you want to validate any JSON value, you can use the snippet below.

const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
);

jsonSchema.parse(data);

感谢 ggoodman 提出此建议。

¥Thanks to ggoodman for suggesting this.

循环对象

¥Cyclical objects

尽管支持递归模式,但在某些情况下将循环数据传递到 Zod 会导致无限循环。

¥Despite supporting recursive schemas, passing cyclical data into Zod will cause an infinite loop in some cases.

要在循环对象引起问题之前检测它们,请考虑 这种方法

¥To detect cyclical objects before they cause problems, consider this approach.

Promise

const numberPromise = z.promise(z.number());

"解析" 与 promise 模式的工作方式略有不同。验证分为两个部分:

¥"Parsing" works a little differently with promise schemas. Validation happens in two parts:

  1. Zod 同步检查输入是否是 Promise 的实例(即具有 .then.catch 方法的对象)。

    ¥Zod synchronously checks that the input is an instance of Promise (i.e. an object with .then and .catch methods.).

  2. Zod 使用 .then 将额外的验证步骤附加到现有的 Promise 上。你必须在返回的 Promise 上使用 .catch 来处理验证失败。

    ¥Zod uses .then to attach an additional validation step onto the existing Promise. You'll have to use .catch on the returned Promise to handle validation failures.

numberPromise.parse("tuna");
// ZodError: Non-Promise type: string

numberPromise.parse(Promise.resolve("tuna"));
// => Promise<number>

const test = async () => {
await numberPromise.parse(Promise.resolve("tuna"));
// ZodError: Non-number type: string

await numberPromise.parse(Promise.resolve(3.14));
// => 3.14
};

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

const blob: any = "whatever";
TestSchema.parse(new Test()); // passes
TestSchema.parse(blob); // throws

函数

¥Functions

Zod 还允许你定义 "函数模式"。这使得验证函数的输入和输出变得容易,而无需混合验证代码和 "业务逻辑"。

¥Zod also lets you define "function schemas". This makes it easy to validate the inputs and outputs of a function without intermixing your validation code and "business logic".

你可以使用 z.function(args, returnType) 创建函数模式。

¥You can create a function schema with z.function(args, returnType) .

const myFunction = z.function();

type myFunction = z.infer<typeof myFunction>;
// => ()=>unknown

定义输入和输出。

¥Define inputs and outputs.

const myFunction = z
.function()
.args(z.string(), z.number()) // accepts an arbitrary number of arguments
.returns(z.boolean());

type myFunction = z.infer<typeof myFunction>;
// => (arg0: string, arg1: number)=>boolean

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

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

const trimmedLength = z
.function()
.args(z.string()) // accepts an arbitrary number of arguments
.returns(z.number())
.implement((x) => {
// TypeScript knows x is a string!
return x.trim().length;
});

trimmedLength("sandwich"); // => 8
trimmedLength(" asdf "); // => 4

如果你只关心验证输入,请不要调用 .returns() 方法。输出类型将从实现中推断出来。

¥If you only care about validating inputs, just don't call the .returns() method. The output type will be inferred from the implementation.

如果你的函数没有返回任何内容,你可以使用特殊的 z.void() 选项。这将让 Zod 正确推断返回 void 的函数的类型。(返回空值的函数实际上返回未定义。)

¥You can use the special z.void() option if your function doesn't return anything. This will let Zod properly infer the type of void-returning functions. (Void-returning functions actually return undefined.)

const myFunction = z
.function()
.args(z.string())
.implement((arg) => {
return [arg.length];
});

myFunction; // (arg: string)=>number[]

从函数模式中提取输入和输出模式。

¥Extract the input and output schemas from a function schema.

myFunction.parameters();
// => ZodTuple<[ZodString, ZodNumber]>

myFunction.returnType();
// => ZodBoolean

预处理

¥Preprocess

Zod 现在支持原始强制,而无需 .preprocess()。有关更多信息,请参阅 强制文档

¥Zod now supports primitive coercion without the need for .preprocess(). See the coercion docs for more information.

通常,Zod 在 "解析然后转换" 范式下运行。Zod 首先验证输入,然后将其传递给一系列转换函数。(有关转换的更多信息,请阅读 .transform 文档。)

¥Typically Zod operates under a "parse then transform" paradigm. Zod validates the input first, then passes it through a chain of transformation functions. (For more information about transforms, read the .transform docs.)

但有时你希望在解析之前对输入应用一些转换。常见用例:类型强制。Zod 使用 z.preprocess() 启用此功能。

¥But sometimes you want to apply some transform to the input before parsing happens. A common use case: type coercion. Zod enables this with the z.preprocess().

const castToString = z.preprocess((val) => String(val), z.string());

这将返回一个 ZodEffects 实例。ZodEffects 是一个封装器类,包含与预处理、细化和转换有关的所有逻辑。

¥This returns a ZodEffects instance. ZodEffects is a wrapper class that contains all logic pertaining to preprocessing, refinements, and transforms.

自定义模式

¥Custom schemas

你可以使用 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");

架构方法

¥Schema methods

所有 Zod 模式都包含某些方法。

¥All Zod schemas contain certain methods.

.parse

.parse(data: unknown): T

给定任何 Zod 模式,你都可以调用其 .parse 方法来检查 data 是否有效。如果是,则返回一个包含完整类型信息的值!否则,将抛出错误。

¥Given any Zod schema, you can call its .parse method to check data is valid. If it is, a value is returned with full type information! Otherwise, an error is thrown.

重要:.parse 返回的值是你传入的变量的深度克隆。

¥IMPORTANT: The value returned by .parse is a deep clone of the variable you passed in.

const stringSchema = z.string();

stringSchema.parse("fish"); // => returns "fish"
stringSchema.parse(12); // throws error

.parseAsync

.parseAsync(data:unknown): Promise<T>

如果你使用异步 refinementstransforms(稍后会详细介绍),则需要使用 .parseAsync

¥If you use asynchronous refinements or transforms (more on those later), you'll need to use .parseAsync.

const stringSchema = z.string().refine(async (val) => val.length <= 8);

await stringSchema.parseAsync("hello"); // => returns "hello"
await stringSchema.parseAsync("hello world"); // => throws error

.safeParse

.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }

如果你不希望 Zod 在验证失败时抛出错误,请使用 .safeParse。此方法返回一个对象,其中包含成功解析的数据或包含有关验证问题的详细信息的 ZodError 实例。

¥If you don't want Zod to throw errors when validation fails, use .safeParse. This method returns an object containing either the successfully parsed data or a ZodError instance containing detailed information about the validation problems.

stringSchema.safeParse(12);
// => { success: false; error: ZodError }

stringSchema.safeParse("billie");
// => { success: true; data: 'billie' }

结果是一个可区分的联合,因此你可以非常方便地处理错误:

¥The result is a discriminated union, so you can handle errors very conveniently:

const result = stringSchema.safeParse("billie");
if (!result.success) {
// handle error then return
result.error;
} else {
// do something
result.data;
}

.safeParseAsync

别名:.spa

¥Alias: .spa

safeParse 的异步版本。

¥An asynchronous version of safeParse.

await stringSchema.safeParseAsync("billie");

为方便起见,这已被别名为 .spa

¥For convenience, this has been aliased to .spa:

await stringSchema.spa("billie");

.refine

.refine(validator: (data:T)=>any, params?: RefineParams)

Zod 可让你通过改进提供自定义验证逻辑。(有关创建多个问题和自定义错误代码等高级功能,请参阅 .superRefine。)

¥Zod lets you provide custom validation logic via refinements. (For advanced features like creating multiple issues and customizing error codes, see .superRefine.)

Zod 的设计尽可能接近 TypeScript。但你可能希望检查许多所谓的 "细化类型",而这些 "细化类型" 无法在 TypeScript 的类型系统中表示。例如:检查数字是否为整数或字符串是否为有效的电子邮件地址。

¥Zod was designed to mirror TypeScript as closely as possible. But there are many so-called "refinement types" you may wish to check for that can't be represented in TypeScript's type system. For instance: checking that a number is an integer or that a string is a valid email address.

例如,你可以使用 .refine 在任何 Zod 模式上定义自定义验证检查:

¥For example, you can define a custom validation check on any Zod schema with .refine :

const myString = z.string().refine((val) => val.length <= 255, {
message: "String can't be more than 255 characters",
});

⚠️ 细化函数不应抛出。相反,它们应该返回一个假值来表示失败。

¥⚠️ Refinement functions should not throw. Instead they should return a falsy value to signal failure.

参数

¥Arguments

如你所见,.refine 接受两个参数。

¥As you can see, .refine takes two arguments.

  1. 第一个是验证函数。此函数接受一个输入(类型 T - 模式的推断类型)并返回 any。任何真值都会通过验证。(在 zod@1.6.2 之前,验证函数必须返回布尔值。)

    ¥The first is the validation function. This function takes one input (of type T — the inferred type of the schema) and returns any. Any truthy value will pass validation. (Prior to zod@1.6.2 the validation function had to return a boolean.)

  2. 第二个参数接受一些选项。你可以使用它来自定义某些错误处理行为:

    ¥The second argument accepts some options. You can use this to customize certain error-handling behavior:

type RefineParams = {
// override error message
message?: string;

// appended to error path
path?: (string | number)[];

// params object you can use to customize message
// in error map
params?: object;
};

对于高级情况,第二个参数也可以是返回 RefineParams 的函数。

¥For advanced cases, the second argument can also be a function that returns RefineParams.

const longString = z.string().refine(
(val) => val.length > 10,
(val) => ({ message: `${val} is not more than 10 characters` })
);

自定义错误路径

¥Customize error 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 of error
});

passwordForm.parse({ password: "asdf", confirm: "qwer" });

因为你提供了 path 参数,所以产生的错误将是:

¥Because you provided a path parameter, the resulting error will be:

ZodError {
issues: [{
"code": "custom",
"path": [ "confirm" ],
"message": "Passwords don't match"
}]
}

异步细化

¥Asynchronous refinements

细化也可以是异步的:

¥Refinements can also be async:

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.

与转换的关系

¥Relationship to transforms

转换和细化可以交错:

¥Transforms and refinements can be interleaved:

z.string()
.transform((val) => val.length)
.refine((val) => val > 25);

.superRefine

.refine 方法实际上是更通用(和详细)的方法 superRefine 之上的语法糖。以下是一个例子:

¥The .refine method is actually syntactic sugar atop a more versatile (and verbose) method called superRefine. Here's an example:

const Strings = z.array(z.string()).superRefine((val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
code: z.ZodIssueCode.too_big,
maximum: 3,
type: "array",
inclusive: true,
message: "Too many items 😡",
});
}

if (val.length !== new Set(val).size) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `No duplicates allowed.`,
});
}
});

你可以根据需要添加任意数量的问题。如果在函数执行期间未调用 ctx.addIssue,则验证通过。

¥You can add as many issues as you like. If ctx.addIssue is not called during the execution of the function, validation passes.

通常,细化总是会产生 ZodIssueCode.custom 错误代码的问题,但使用 superRefine 可能会引发任何 ZodIssueCode 的问题。每个问题代码在错误处理指南中都有详细描述:ERROR_HANDLING.md

¥Normally refinements always create issues with a ZodIssueCode.custom error code, but with superRefine it's possible to throw issues of any ZodIssueCode. Each issue code is described in detail in the Error Handling guide: ERROR_HANDLING.md.

提前中止

¥Abort early

默认情况下,即使细化检查失败,解析仍将继续。例如,如果你将多个细化链接在一起,它们都将被执行。但是,最好尽早中止以防止执行后续改进。要实现这一点,请将 fatal 标志传递给 ctx.addIssue 并返回 z.NEVER

¥By default, parsing will continue even after a refinement check fails. For instance, if you chain together multiple refinements, they will all be executed. However, it may be desirable to abort early to prevent later refinements from being executed. To achieve this, pass the fatal flag to ctx.addIssue and return z.NEVER.

const schema = z.number().superRefine((val, ctx) => {
if (val < 10) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "should be >= 10",
fatal: true,
});

return z.NEVER;
}

if (val !== 12) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "should be twelve",
});
}
});

类型改进

¥Type refinements

如果你将 类型谓词 提供给 .refine().superRefine(),则结果类型将缩小到谓词的类型。如果你混合了多个链式细化和转换,这将很有用:

¥If you provide a type predicate to .refine() or .superRefine(), the resulting type will be narrowed down to your predicate's type. This is useful if you are mixing multiple chained refinements and transformations:

const schema = z
.object({
first: z.string(),
second: z.number(),
})
.nullable()
.superRefine((arg, ctx): arg is { first: string; second: number } => {
if (!arg) {
ctx.addIssue({
code: z.ZodIssueCode.custom, // customize your issue
message: "object should exist",
});
}

return z.NEVER; // The return value is not used, but we need to return something to satisfy the typing
})
// here, TS knows that arg is not null
.refine((arg) => arg.first === "bob", "`first` is not `bob`!");

⚠️ 你必须使用 ctx.addIssue() 而不是返回布尔值来指示验证是否通过。如果在函数执行期间未调用 ctx.addIssue,则验证通过。

¥⚠️ You must use ctx.addIssue() instead of returning a boolean value to indicate whether the validation passes. If ctx.addIssue is not called during the execution of the function, validation passes.

.transform

要在解析后转换数据,请使用 transform 方法。

¥To transform data after parsing, use the transform method.

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

stringToNumber.parse("string"); // => 6

链接顺序

¥Chaining order

请注意,上面的 stringToNumberZodEffects 子类的一个实例。它不是 ZodString 的实例。如果你想使用 ZodString 的内置方法(例如 .email()),则必须在任何转换之前应用这些方法。

¥Note that stringToNumber above is an instance of the ZodEffects subclass. It is NOT an instance of ZodString. If you want to use the built-in methods of ZodString (e.g. .email()) you must apply those methods before any transforms.

const emailToDomain = z
.string()
.email()
.transform((val) => val.split("@")[1]);

emailToDomain.parse("colinhacks@example.com"); // => example.com

转换期间验证

¥Validating during transform

.transform 方法可以同时验证和转换值。这通常比链接 transformrefine 更简单,重复性更低。

¥The .transform method can simultaneously validate and transform the value. This is often simpler and less duplicative than chaining transform and refine.

.superRefine 一样,转换函数接收一个 ctx 对象,该对象具有可用于注册验证问题的 addIssue 方法。

¥As with .superRefine, the transform function receives a ctx object with an addIssue method that can be used to register validation issues.

const numberInString = z.string().transform((val, ctx) => {
const parsed = parseInt(val);
if (isNaN(parsed)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Not a number",
});

// This is a special symbol you can use to
// return early from the transform function.
// It has type `never` so it does not affect the
// inferred return type.
return z.NEVER;
}
return parsed;
});

与细化的关系

¥Relationship to refinements

转换和细化可以交错。这些将按照声明的顺序执行。

¥Transforms and refinements can be interleaved. These will be executed in the order they are declared.

const nameToGreeting = z
.string()
.transform((val) => val.toUpperCase())
.refine((val) => val.length > 15)
.transform((val) => `Hello ${val}`)
.refine((val) => val.indexOf("!") === -1);

异步转换

¥Async transforms

转换也可以是异步的。

¥Transforms can also be async.

const IdToUser = z
.string()
.uuid()
.transform(async (id) => {
return await getUserById(id);
});

⚠️ 如果你的模式包含异步转换,则必须使用 .parseAsync() 或 .safeParseAsync() 来解析数据。否则 Zod 将抛出错误。

¥⚠️ If your schema contains asynchronous transforms, you must use .parseAsync() or .safeParseAsync() to parse data. Otherwise Zod will throw an error.

.default

你可以使用转换在 Zod 中实现 "默认值" 的概念。

¥You can use transforms to implement the concept of "default values" in Zod.

const stringWithDefault = z.string().default("tuna");

stringWithDefault.parse(undefined); // => "tuna"

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

¥Optionally, you can pass a function into .default that will be re-executed whenever a default value needs to be generated:

const numberWithRandomDefault = z.number().default(Math.random);

numberWithRandomDefault.parse(undefined); // => 0.4413456736055323
numberWithRandomDefault.parse(undefined); // => 0.1871840107401901
numberWithRandomDefault.parse(undefined); // => 0.7223408162401552

从概念上讲,Zod 处理默认值的方式如下:

¥Conceptually, this is how Zod processes default values:

  1. 如果输入是 undefined,则返回默认值

    ¥If the input is undefined, the default value is returned

  2. 否则,使用基本模式解析数据

    ¥Otherwise, the data is parsed using the base schema

.describe

使用 .describe()description 属性添加到生成的模式。

¥Use .describe() to add a description property to the resulting schema.

const documentedString = z
.string()
.describe("A useful bit of text, if you know what to do with it.");
documentedString.description; // A useful bit of text…

这对于记录字段很有用,例如在 JSON 模式中使用 zod-to-json-schema 之类的库)。

¥This can be useful for documenting a field, for example in a JSON Schema using a library like zod-to-json-schema).

.catch

使用 .catch() 提供在发生解析错误时要返回的 "捕获值"。

¥Use .catch() to provide a "catch value" to be returned in the event of a parsing error.

const numberWithCatch = z.number().catch(42);

numberWithCatch.parse(5); // => 5
numberWithCatch.parse("tuna"); // => 42

或者,你可以将一个函数传递到 .catch 中,该函数将在需要生成默认值时重新执行。包含捕获的错误的 ctx 对象将传递到此函数中。

¥Optionally, you can pass a function into .catch that will be re-executed whenever a default value needs to be generated. A ctx object containing the caught error will be passed into this function.

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

从概念上讲,Zod 处理 "捕获值" 的方式如下:

¥Conceptually, this is how Zod processes "catch values":

  1. 使用基本模式解析数据

    ¥The data is parsed using the base schema

  2. 如果解析失败,则返回 "捕获值"

    ¥If the parsing fails, the "catch value" is returned

.optional

返回可选版本模式的便捷方法。

¥A convenience method that returns an optional version of a schema.

const optionalString = z.string().optional(); // string | undefined

// equivalent to
z.optional(z.string());

.nullable

返回可空版本模式的便捷方法。

¥A convenience method that returns a nullable version of a schema.

const nullableString = z.string().nullable(); // string | null

// equivalent to
z.nullable(z.string());

.nullish

返回 "nullish" 版本模式的便捷方法。Nullish 模式将同时接受 undefinednull。阅读有关 "nullish" 在 TypeScript 3.7 发行说明中 概念的更多信息。

¥A convenience method that returns a "nullish" version of a schema. Nullish schemas will accept both undefined and null. Read more about the concept of "nullish" in the TypeScript 3.7 release notes.

const nullishString = z.string().nullish(); // string | null | undefined

// equivalent to
z.string().nullable().optional();

.array

返回给定类型的数组模式的便捷方法:

¥A convenience method that returns an array schema for the given type:

const stringArray = z.string().array(); // string[]

// equivalent to
z.array(z.string());

.promise

promise 类型的便捷方法:

¥A convenience method for promise types:

const stringPromise = z.string().promise(); // Promise<string>

// equivalent to
z.promise(z.string());

.or

联合类型 的便捷方法。

¥A convenience method for union types.

const stringOrNumber = z.string().or(z.number()); // string | number

// equivalent to
z.union([z.string(), z.number()]);

.and

创建交叉类型的便捷方法。

¥A convenience method for creating intersection types.

const nameAndAge = z
.object({ name: z.string() })
.and(z.object({ age: z.number() })); // { name: string } & { age: number }

// equivalent to
z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));

.brand

.brand<T>() => ZodBranded<this, B>

TypeScript 的类型系统是结构化的,这意味着任何两种结构等效的类型都被视为相同的。

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

type Cat = { name: string };
type Dog = { name: string };

const petCat = (cat: Cat) => {};
const fido: Dog = { name: "fido" };
petCat(fido); // works fine

在某些情况下,在 TypeScript 中模拟名义类型可能是可取的。例如,你可能希望编写一个仅接受已由 Zod 验证的输入的函数。这可以通过品牌类型(又称不透明类型)来实现。

¥In some cases, its can be desirable to simulate nominal typing inside TypeScript. For instance, you may wish to write a function that only accepts an input that has been validated by Zod. This can be achieved with branded types (AKA opaque types).

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

const petCat = (cat: Cat) => {};

// this works
const simba = Cat.parse({ name: "simba" });
petCat(simba);

// this doesn't
petCat({ name: "fido" });

在底层,它通过使用交集类型将 "brand" 附加到推断类型来实现。这样,普通/无品牌的数据结构不再可分配给模式的推断类型。

¥Under the hood, this works by attaching a "brand" to the inferred type using an intersection type. This way, plain/unbranded data structures are no longer assignable to the inferred type of the schema.

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

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

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

.readonly

.readonly() => ZodReadonly<this>

此方法返回一个 ZodReadonly 模式实例,该实例使用基本模式解析输入,然后在结果上调用 Object.freeze()。推断类型也标记为 readonly

¥This method returns a ZodReadonly schema instance that parses the input using the base schema, then calls Object.freeze() on the result. The inferred type is also marked as readonly.

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

const result = schema.parse({ name: "fido" });
result.name = "simba"; // error

推断类型在相关时使用 TypeScript 的内置只读类型。

¥The inferred type uses TypeScript's built-in readonly types when relevant.

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>

.pipe

模式可以链接到验证 "pipelines"。它对于在 .transform() 之后轻松验证结果很有用:

¥Schemas can be chained into validation "pipelines". It's useful for easily validating the result after a .transform():

z.string()
.transform((val) => val.length)
.pipe(z.number().min(5));

.pipe() 方法返回一个 ZodPipeline 实例。

¥The .pipe() method returns a ZodPipeline instance.

你可以使用 .pipe() 修复 z.coerce 的常见问题。

¥You can use .pipe() to fix common issues with z.coerce.

你可以将输入限制为与你选择的强制转换配合良好的类型。然后使用 .pipe() 应用强制。

¥You can constrain the input to types that work well with your chosen coercion. Then use .pipe() to apply the coercion.

不使用受限输入:

¥without constrained input:

const toDate = z.coerce.date();

// works intuitively
console.log(toDate.safeParse("2023-01-01").success); // true

// might not be what you want
console.log(toDate.safeParse(null).success); // true

使用受限输入:

¥with constrained input:

const datelike = z.union([z.number(), z.string(), z.date()]);
const datelikeToDate = datelike.pipe(z.coerce.date());

// still works intuitively
console.log(datelikeToDate.safeParse("2023-01-01").success); // true

// more likely what you want
console.log(datelikeToDate.safeParse(null).success); // false

你还可以使用此技术来避免引发未捕获错误的强制转换。

¥You can also use this technique to avoid coercions that throw uncaught errors.

不使用受限输入:

¥without constrained input:

const toBigInt = z.coerce.bigint();

// works intuitively
console.log(toBigInt.safeParse("42")); // true

// probably not what you want
console.log(toBigInt.safeParse(null)); // throws uncaught error

使用受限输入:

¥with constrained input:

const toNumber = z.number().or(z.string()).pipe(z.coerce.number());
const toBigInt = z.bigint().or(toNumber).pipe(z.coerce.bigint());

// still works intuitively
console.log(toBigInt.safeParse("42").success); // true

// error handled by zod, more likely what you want
console.log(toBigInt.safeParse(null).success); // false

指南和概念

¥Guides and concepts

类型推断

¥Type inference

你可以使用 z.infer<typeof mySchema> 提取任何模式的 TypeScript 类型。

¥You can extract the TypeScript type of any schema with z.infer<typeof mySchema> .

const A = z.string();
type A = z.infer<typeof A>; // string

const u: A = 12; // TypeError
const u: A = "asdf"; // compiles

那么转换呢?

¥What about transforms?

实际上,每个 Zod 模式内部跟踪两种类型:输入和输出。对于大多数模式(例如 z.string()),这两个是相同的。但一旦你将转换添加到组合中,这两个值可能会有所不同。例如,z.string().transform(val => val.length) 的输入为 string,输出为 number

¥In reality each Zod schema internally tracks two types: an input and an output. For most schemas (e.g. z.string()) these two are the same. But once you add transforms into the mix, these two values can diverge. For instance z.string().transform(val => val.length) has an input of string and an output of number.

你可以像这样分别提取输入和输出类型:

¥You can separately extract the input and output types like so:

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

// ⚠️ Important: z.infer returns the OUTPUT type!
type input = z.input<typeof stringToNumber>; // string
type output = z.output<typeof stringToNumber>; // number

// equivalent to z.output!
type inferred = z.infer<typeof stringToNumber>; // number

编写通用函数

¥Writing generic functions

使用 TypeScript 泛型,可以编写接受 Zod 模式作为参数的可重用函数。这使你能够创建自定义验证逻辑、模式转换等,同时保持类型安全和推断。

¥With TypeScript generics, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference.

尝试编写接受 Zod 模式作为输入的函数时,很容易尝试以下方法:

¥When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this:

function inferSchema<T>(schema: z.ZodType<T>) {
return schema;
}

这种方法是不正确的,并且限制了 TypeScript 正确推断参数的能力。无论你传入什么,schema 的类型都将是 ZodType 的一个实例。

¥This approach is incorrect, and limits TypeScript's ability to properly infer the argument. No matter what you pass in, the type of schema will be an instance of ZodType.

inferSchema(z.string());
// => ZodType<string>

这种方法会丢失类型信息,即输入实际上是哪个子类(在本例中为 ZodString)。这意味着你无法在 inferSchema 的结果上调用任何特定于字符串的方法(如 .min())。

¥This approach loses type information, namely which subclass the input actually is (in this case, ZodString). That means you can't call any string-specific methods like .min() on the result of inferSchema.

更好的方法是推断整个模式,而不仅仅是其推断的类型。你可以使用名为 z.ZodTypeAny 的工具类型执行此操作。

¥A better approach is to infer the schema as a whole instead of merely its inferred type. You can do this with a utility type called z.ZodTypeAny.

function inferSchema<T extends z.ZodTypeAny>(schema: T) {
return schema;
}

inferSchema(z.string());
// => ZodString

ZodTypeAny 只是 ZodType<any, any, any> 的简写,这种类型足够广泛,可以匹配任何 Zod 模式。

¥ZodTypeAny is just a shorthand for ZodType<any, any, any>, a type that is broad enough to match any Zod schema.

结果现在已完全正确输入,并且类型系统可以推断出模式的特定子类。

¥The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema.

推断推断类型

¥Inferring the inferred type

如果你遵循使用 z.ZodTypeAny 作为模式的通用参数的最佳实践,则可能会遇到解析数据被键入为 any 而不是模式的推断类型的问题。

¥If you follow the best practice of using z.ZodTypeAny as the generic parameter for your schema, you may encounter issues with the parsed data being typed as any instead of the inferred type of the schema.

function parseData<T extends z.ZodTypeAny>(data: unknown, schema: T) {
return schema.parse(data);
}

parseData("sup", z.string());
// => any

由于 TypeScript 推断的工作方式,它将 schema 视为 ZodTypeAny,而不是推断的类型。你可以使用 z.infer 进行类型转换来修复此问题。

¥Due to how TypeScript inference works, it is treating schema like a ZodTypeAny instead of the inferred type. You can fix this with a type cast using z.infer.

function parseData<T extends z.ZodTypeAny>(data: unknown, schema: T) {
return schema.parse(data) as z.infer<T>;
// ^^^^^^^^^^^^^^ <- add this
}

parseData("sup", z.string());
// => string

限制允许的输入

¥Constraining allowable inputs

ZodType 类有三个泛型参数。

¥The ZodType class has three generic parameters.

class ZodType<
Output = any,
Def extends ZodTypeDef = ZodTypeDef,
Input = Output
> { ... }

通过在通用输入中限制这些,你可以限制哪些模式可以作为函数的输入:

¥By constraining these in your generic input, you can limit what schemas are allowable as inputs to your function:

function makeSchemaOptional<T extends z.ZodType<string>>(schema: T) {
return schema.optional();
}

makeSchemaOptional(z.string());
// works fine

makeSchemaOptional(z.number());
// Error: 'ZodNumber' is not assignable to parameter of type 'ZodType<string, ZodTypeDef, string>'

错误处理

¥Error handling

Zod 提供了一个名为 ZodError 的 Error 子类。ZodErrors 包含一个 issues 数组,其中包含有关验证问题的详细信息。

¥Zod provides a subclass of Error called ZodError. ZodErrors contain an issues array containing detailed information about the validation problems.

const result = z
.object({
name: z.string(),
})
.safeParse({ name: 12 });

if (!result.success) {
result.error.issues;
/* [
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"path": [ "name" ],
"message": "Expected string, received number"
}
] */
}

有关可能的错误代码以及如何自定义错误消息的详细信息,请查看专用的错误处理指南:ERROR_HANDLING.md

¥For detailed information about the possible error codes and how to customize error messages, check out the dedicated error handling guide: ERROR_HANDLING.md

Zod 的错误报告强调完整性和正确性。如果你希望向终端用户显示有用的错误消息,则应该使用错误映射覆盖 Zod 的错误消息(在错误处理指南中详细描述)或使用第三方库(如 zod-validation-error

¥Zod's error reporting emphasizes completeness and correctness. If you are looking to present a useful error message to the end user, you should either override Zod's error messages using an error map (described in detail in the Error Handling guide) or use a third-party library like zod-validation-error

错误格式

¥Error formatting

你可以使用 .format() 方法将此错误转换为嵌套对象。

¥You can use the .format() method to convert this error into a nested object.

const result = z
.object({
name: z.string(),
})
.safeParse({ name: 12 });

if (!result.success) {
const formatted = result.error.format();
/* {
name: { _errors: [ 'Expected string, received number' ] }
} */

formatted.name?._errors;
// => ["Expected string, received number"]
}

比较

¥Comparison

还有少数其他广泛使用的验证库,但它们都存在某些设计限制,导致开发者体验不理想。

¥There are a handful of other widely-used validation libraries, but all of them have certain design limitations that make for a non-ideal developer experience.

Joi

https://github.com/hapijs/joi

不支持静态类型推断 😕

¥Doesn't support static type inference 😕

是的

¥Yup

https://github.com/jquense/yup

Yup 是一个功能齐全的库,首先在 vanilla JS 中实现,后来在 TypeScript 中重写。

¥Yup is a full-featured library that was implemented first in vanilla JS, and later rewritten in TypeScript.

  • 支持强制转换和转换

    ¥Supports casting and transforms

  • 默认情况下,所有对象字段都是可选的

    ¥All object fields are optional by default

  • 缺少 promise 模式

    ¥Missing promise schemas

  • 缺少函数模式

    ¥Missing function schemas

  • 缺少联合和交集模式

    ¥Missing union & intersection schemas

io-ts

https://github.com/gcanti/io-ts

io-ts 是 gcanti 的一个优秀库。io-ts 的 API 极大地启发了 Zod 的设计。

¥io-ts is an excellent library by gcanti. The API of io-ts heavily inspired the design of Zod.

根据我们的经验,io-ts 在许多情况下优先考虑函数式编程的纯度而不是开发者的经验。这是一个有效且令人钦佩的设计目标,但它使 io-ts 特别难以集成到具有更多程序化或面向对象偏见的现有代码库中。例如,考虑如何在 io-ts 中定义具有可选属性的对象:

¥In our experience, io-ts prioritizes functional programming purity over developer experience in many cases. This is a valid and admirable design goal, but it makes io-ts particularly hard to integrate into an existing codebase with a more procedural or object-oriented bias. For instance, consider how to define an object with optional properties in io-ts:

import * as t from "io-ts";

const A = t.type({
foo: t.string,
});

const B = t.partial({
bar: t.number,
});

const C = t.intersection([A, B]);

type C = t.TypeOf<typeof C>;
// returns { foo: string; bar?: number | undefined }

你必须在单独的对象验证器中定义必需和可选的 props,将可选项传递给 t.partial(将所有属性标记为可选),然后将它们与 t.intersection 组合。

¥You must define the required and optional props in separate object validators, pass the optionals through t.partial (which marks all properties as optional), then combine them with t.intersection .

考虑 Zod 中的等效项:

¥Consider the equivalent in Zod:

const C = z.object({
foo: z.string(),
bar: z.number().optional(),
});

type C = z.infer<typeof C>;
// returns { foo: string; bar?: number | undefined }

这个更具声明性的 API 使模式定义更加简洁。

¥This more declarative API makes schema definitions vastly more concise.

io-ts 还需要使用 gcanti 的函数式编程库 fp-ts 来解析结果和处理错误。对于希望保持代码库严格功能的开发者来说,这是另一个极好的资源。但依赖 fp-ts 必然会带来大量的智力开销;开发者必须熟悉函数式编程概念和 fp-ts 命名法才能使用该库。

¥io-ts also requires the use of gcanti's functional programming library fp-ts to parse results and handle errors. This is another fantastic resource for developers looking to keep their codebase strictly functional. But depending on fp-ts necessarily comes with a lot of intellectual overhead; a developer has to be familiar with functional programming concepts and the fp-ts nomenclature to use the library.

  • 支持具有序列化和反序列化转换的编解码器

    ¥Supports codecs with serialization & deserialization transforms

  • 支持品牌类型

    ¥Supports branded types

  • 支持高级函数式编程、更高级类型、fp-ts 兼容性

    ¥Supports advanced functional programming, higher-kinded types, fp-ts compatibility

  • 缺少对象方法:(pick、omit、partial、deepPartial、merge、extend)

    ¥Missing object methods: (pick, omit, partial, deepPartial, merge, extend)

  • 缺少具有正确类型的非空数组([T, ...T[]]

    ¥Missing nonempty arrays with proper typing ([T, ...T[]])

  • 缺少 promise 模式

    ¥Missing promise schemas

  • 缺少函数模式

    ¥Missing function schemas

运行类型

¥Runtypes

https://github.com/pelotom/runtypes

良好的类型推断支持。

¥Good type inference support.

  • 支持 "模式匹配":分布在联合上的计算属性

    ¥Supports "pattern matching": computed properties that distribute over unions

  • 缺少对象方法:(deepPartial,合并)

    ¥Missing object methods: (deepPartial, merge)

  • 缺少具有正确类型的非空数组([T, ...T[]]

    ¥Missing nonempty arrays with proper typing ([T, ...T[]])

  • 缺少 promise 模式

    ¥Missing promise schemas

  • 缺少错误自定义

    ¥Missing error customization

Ow

https://github.com/sindresorhus/ow

Ow 专注于函数输入验证。它是一个可以轻松表达复杂断言语句的库,但它不允许你解析非类型化数据。它们支持更广泛的类型;Zod 与 TypeScript 的类型系统几乎是一一对应的,而 ow 可让你开箱即用地验证几种高度特定的类型(例如 int32Array ,请参阅其 README 中的完整列表)。

¥Ow is focused on function input validation. It's a library that makes it easy to express complicated assert statements, but it doesn't let you parse untyped data. They support a much wider variety of types; Zod has a nearly one-to-one mapping with TypeScript's type system, whereas ow lets you validate several highly-specific types out of the box (e.g. int32Array , see full list in their README).

如果你想验证函数输入,请在 Zod 中使用函数模式!这是一种更简单的方法,可让你重用函数类型声明而无需重复自己(即在每个函数的开头复制粘贴一堆 ow 断言)。此外,Zod 还允许你验证返回类型,因此你可以确保不会有任何意外数据传递到下游。

¥If you want to validate function inputs, use function schemas in Zod! It's a much simpler approach that lets you reuse a function type declaration without repeating yourself (namely, copy-pasting a bunch of ow assertions at the beginning of every function). Also Zod lets you validate your return types as well, so you can be sure there won't be any unexpected data passed downstream.

更新日志

¥Changelog

查看 CHANGELOG.md 的变更日志

¥View the changelog at CHANGELOG.md