如何用 Zod 的 registry 集中管理 schema 的 description、examples 等元数据?
【免费下载链接】zodTypeScript-first schema validation with static type inference项目地址: https://gitcode.com/GitHub_Trending/zo/zod
当你的 Zod schema 不只是用来做运行时校验,还要参与文档生成、AI 结构化输出、表单校验或 JSON Schema 导出时,你经常需要给每个 schema 附带额外的说明信息:字段描述、示例值、标题、是否废弃等。Zod 4 用registry(注册表)来承担这件事:registry 是一组 schema 的集合,每个 schema 关联一份强类型的元数据。本文以仓库 packages/docs/content/metadata.mdx 的官方文档为主线,结合 packages/docs/content/json-schema.mdx 说明如何创建 registry、注册元数据、做类型约束,以及如何验证元数据确实被读到了。当前仓库中的 zod 版本为 4.5.4(见 packages/zod/package.json),registry 属于 Zod 4 的 API。
准备工作
安装 zod:
npm install zod代码中通过import * as z from "zod"引入。
创建自定义 registry 并注册元数据
用z.registry<T>()创建一个带元数据类型的 registry,T描述每个 schema 的元数据形状:
import * as z from "zod"; const myRegistry = z.registry<{ description: string }>();然后对这个 registry 做注册、查询、删除:
const mySchema = z.string(); myRegistry.add(mySchema, { description: "A cool schema!" }); myRegistry.has(mySchema); // => true myRegistry.get(mySchema); // => { description: "A cool schema!" } myRegistry.remove(mySchema); myRegistry.clear(); // wipe registryTypeScript 会强制每个 schema 的元数据符合 registry 声明的元数据类型:
myRegistry.add(mySchema, { description: "A cool schema!" }); // ✅ myRegistry.add(mySchema, { description: 123 }); // ❌另一种写法是调用 schema 自己的.register()方法,把 schema 加入指定 registry。.register()与其他 Zod 方法不同:它不返回新 schema,而是返回原 schema(.meta()、.describe()都会返回新实例)。因此可以在定义 schema 时“就地”写元数据:
const mySchema = z.string(); mySchema.register(myRegistry, { description: "A cool schema!" }); // => mySchemaconst mySchema = z.object({ name: z.string().register(myRegistry, { description: "The user's name" }), age: z.number().register(myRegistry, { description: "The user's age" }), });如果创建 registry 时没有指定元数据类型,它就是一个不带元数据的普通“集合”:
const myRegistry = z.registry(); myRegistry.add(z.string()); myRegistry.add(z.number());用 z.globalRegistry 集中存放 description、examples 等公共元数据
除了自定义 registry,Zod 提供了一个全局 registryz.globalRegistry,可用于 JSON Schema 生成等场景。它接受的元数据形状是GlobalMeta接口:
export interface GlobalMeta { id?: string ; title?: string ; description?: string; deprecated?: boolean; [k: string]: unknown; }由于examples等字段不在GlobalMeta的固定字段里(但被[k: string]: unknown索引签名放行),官方建议用declaration merging在全局扩充该接口。文档给出的约定是在项目根目录创建一个zod.d.ts文件(“常见约定”,而非强制要求):
declare module "zod" { interface GlobalMeta { // add new fields here examples?: unknown[]; } } // forces TypeScript to consider the file a module export {}扩充之后就可以向z.globalRegistry注册完整的元数据:
import * as z from "zod"; const emailSchema = z.email().register(z.globalRegistry, { id: "email_address", title: "Email address", description: "Your email address", examples: ["first.last@example.com"] });更省事的写法是.meta()方法,它直接注册到z.globalRegistry:
const emailSchema = z.email().meta({ id: "email_address", title: "Email address", description: "Please enter a valid email address", });.describe()则是只注册description字段的简写:
const emailSchema = z.email(); emailSchema.describe("An email address"); // equivalent to emailSchema.meta({ description: "An email address" });文档说明.describe()仍然可用,但.meta()是推荐方式。Zod Mini 中没有.meta()的等价链式方法,文档给出的对应写法是z.email().check(z.meta({...}))/z.email().check(z.describe("...")),这里不展开。
让元数据引用 schema 的推断类型、约束 schema 类型
元数据里最有用的一类字段是“示例值”:它天然应该与 schema 的类型匹配。文档给出了两个进阶技巧。
引用推断类型:特殊符号z.$output指向 schema 的推断输出类型(即z.infer<typeof schema>),z.$input指向输入类型。用它定义元数据,examples就会按各 schema 的实际类型校验:
import * as z from "zod"; type MyMeta = { examples: z.$output[] }; const myRegistry = z.registry<MyMeta>(); myRegistry.add(z.string(), { examples: ["hello", "world"] }); myRegistry.add(z.number(), { examples: [1, 2, 3] });约束 schema 类型:给z.registry()传第二个泛型,限制可以加入该 registry 的 schema 类型。下面的 registry 只接受字符串 schema:
import * as z from "zod"; const myRegistry = z.registry<{ description: string }, z.ZodString>(); myRegistry.add(z.string(), { description: "A number" }); // ✅ myRegistry.add(z.number(), { description: "A number" }); // ❌ // ^ 'ZodNumber' is not assignable to parameter of type 'ZodString'验证元数据是否注册成功、是否生效
验证方式有三种,按强度递增:
1. 无参.meta()取回元数据。调用.meta()不带参数时会取回该 schema 的元数据:
emailSchema.meta(); // => { id: "email_address", title: "Email address", ... }2. 用 registry 的get/has检查(如第一节示例所示,get返回注册的元数据对象,has返回boolean)。
3. 转换到 JSON Schema,确认元数据被复制进结果。在 packages/docs/content/json-schema.mdx 中,z.toJSONSchema()的第二个参数可以显式传入一个 registry(metadata参数),转换时会用它查找每个 schema 的元数据:
z.toJSONSchema(schema, { // ...params metadata: $ZodRegistry<Record<string, any>>; })具体行为(文档示例输出):
// `.meta()` is a convenience method for registering a schema in `z.globalRegistry` const emailSchema = z.string().meta({ title: "Email address", description: "Your email address", }); z.toJSONSchema(emailSchema); // => { type: "string", title: "Email address", description: "Your email address", ... }所有元数据字段都会被复制进 JSON Schema 结果,包括自定义字段:
const schema = z.string().meta({ whatever: 1234 }); z.toJSONSchema(schema); // => { type: "string", whatever: 1234 }元数据优先于 Zod 自动生成的关键字,例如z.toJSONSchema(z.string().meta({ type: "number" }))的结果是{ type: "number" }。如果只想丢弃全部元数据,传一个空 registry:z.toJSONSchema(schema, { metadata: z.registry() })。
限制与注意点
- 元数据绑定在具体的 schema 实例上。Zod 的方法都是不可变的,永远返回新实例,衍生出来的 schema 不会自动“继承”到新实例的
.meta()结果。文档示例:
const A = z.string().meta({ description: "A cool string" }); A.meta(); // => { description: "A cool string" } const B = A.refine(_ => true); B.meta(); // => undefined也就是说,集中管理时要以“最终参与校验/导出的那个 schema 实例”为准注册元数据,中间衍生的实例需要时单独注册。(注意:自定义 registry 的get()在实现中会向schema._zod.parent继承父实例元数据且继承时剔除id,见 packages/zod/src/v4/core/registries.ts 中get方法;GlobalMeta本身没有这种继承说明,两者不要混用。)
id字段被特殊处理。metadata 文档提示:若多个 schema 以相同的id值注册(包括全局 registry),会抛出Error。在 JSON Schema 转换路径上有具体表现,仓库测试 packages/zod/src/v4/classic/tests/registries.test.ts 展示了:两个不同 schema 共用同一id并一起转换时,z.toJSONSchema抛出Duplicate schema id "duplicate-id" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.;而同一 schema 实例重复出现(同id)则不报错,会正常生成$defs。另外该测试还显示,add重复注册同一id本身不抛错(后注册的 schema 覆盖_idmap中的记录),错误在转换时暴露——即“重复id会报错”这一保证的触发点在 JSON Schema 转换阶段。.register()返回原 schema,其余如.meta()、.describe()返回新实例。批量给字段注册元数据时用.register()可以保持 schema 引用不变,便于之后用同一实例做registry.get()/has()校验。
参考
- 元数据与 registry 文档:packages/docs/content/metadata.mdx
- JSON Schema 转换(
metadata参数、target、unrepresentable等):packages/docs/content/json-schema.mdx - registry 实现(
$ZodRegistry、globalRegistry、GlobalMeta):packages/zod/src/v4/core/registries.ts - registry 行为测试:packages/zod/src/v4/classic/tests/registries.test.ts
【免费下载链接】zodTypeScript-first schema validation with static type inference项目地址: https://gitcode.com/GitHub_Trending/zo/zod
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考