创建日期:2026-09-15 | 最近更新:2026-09-15 本文四处结论均为本机实测(zod 4.5.4 / Node 24):Standard Schema 接口、
toJSONSchema()输出、自定义check的 issue、zod/compile的解析加速。数字与输出都是真的。
Zod 深潜 3:zod/mini、Standard Schema、JSON Schema 与自定义扩展
前两篇讲「类型怎么推」「源码怎么跑」。这篇讲工程化四件套:要不要用
zod/mini?怎么和别的库互通(Standard Schema)?怎么和 JSON Schema 互转?以及怎么在 schema 里写自己的校验逻辑。
1. zod/mini:更小、更“函数式”
v4 提供了一个极简入口 zod/mini。它的思路是:默认不挂便利方法,用 check() 显式拼,从而更好 tree-shake。
// 常规 zod
z.string().min(3);
// zod/mini
import * as z from 'zod/mini';
z.string().check(z.minLength(3));
实测(本机真跑):
mini keys: $brand,$input,$output,NEVER,TimePrecision,…
mini 校验: "too_small" ← mini.z.string().check(mini.z.minLength(3)).safeParse('ab')
怎么选:
- 前端/库体积敏感 → 用
mini(代价:API 更啰嗦,很多便利方法要显式check); - 业务代码/需要最全 API 与生态 → 用常规
zod; - 两者不要混用同一份 schema 定义(不同入口的对象不是同一套)。
2. Standard Schema:让 schema 能被「任何工具」消费
Standard Schema 是一个跨库规范:只要实现一个 ~standard 属性,任何工具都能校验你的数据——不用关心你用的是 zod、valibot 还是 arktype。zod v4 内置实现了它。
实测(本机真跑):
const std = z.string()['~standard'];
std.version // 1
std.vendor // "zod"
await std.validate('hi') // → { value: 'hi' }
这带来一个很实际的好处:你写库/写组件时,参数类型可以声明成「任意 Standard Schema」,用户拿 zod/valibot 都能传——生态耦合度立刻下降。
对照:
z.string().parse()是 zod 自己的 API(抛ZodError);~standard.validate()是跨库的统一出口(返回结果对象,不抛)。
3. JSON Schema 互转:toJSONSchema()
需要把 schema 交给外部系统时(OpenAPI 文档、表单引擎、MCP/LLM 工具参数),JSON Schema 是通用语言。v4 原生支持:
const S = z.object({
name: z.string().min(1),
age: z.number().int().optional(),
});
console.log(JSON.stringify(S.toJSONSchema(), null, 2));
实测输出(真实):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"age": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }
},
"required": ["name"],
"additionalProperties": false
}
三个要注意的细节(都是从这份真实输出里读出来的):
.optional()不会进required——required只有name;这是校验语义的正确映射;- 对象默认
additionalProperties: false—— 转成 JSON Schema 后更严格(这与运行时z.object()默认「剥离未知键」略有差别,跨系统时要知道); .int()会补上安全整数范围(±9007199254740991)——因为 JSON Schema 没有「int32」这种概念,只能这么表达。
反过来(JSON Schema → zod)可用社区工具,或按需手写;本节只讲 zod → JSON Schema 这个官方原生能力。
4. 自定义校验:check() 直接操作 issues
前几篇一直说「失败是把 issue 塞进数组」。v4 允许你自己塞:
const Even = z.number().check((ctx) => {
if (typeof ctx.value === 'number' && ctx.value % 2 !== 0) {
ctx.issues.push({ code: 'custom', message: '必须是偶数', input: ctx.value });
}
});
实测结果(真实):
{ "code": "custom", "message": "必须是偶数", "path": [] }
.check(fn)是最底层的自定义校验:你能看到当前ctx.value,也能往ctx.issuespush 任意 issue(含自定义code);- 日常更常用的是它的两个「语法糖」:
.refine(fn, { message }):一个布尔判断,失败产出一个 issue;.superRefine((val, ctx) => …):能一次产出多条、能控制path/code——它本质就是.check()的易用封装;
- 它们都不改变类型(对比
transform,见 类型推导那篇):校验类 API 的 output 与 input 保持一致。
5. 性能开关:zod/compile
v4 还藏了一个「编译器」:import 'zod/compile' 之后,schema 的解析器会被编译成更快的实现。实测(真实数据):
20 万次 parse: 未编译 60.5ms → 编译后 40.5ms (1.50x)
同一份 z.object({ id: z.string().min(3), n: z.number().min(0), tags: z.array(z.string()).optional() }),预热后跑 20 万次 parse:从 60.5ms 降到 40.5ms,约 1.5 倍。
用法与注意:
- 只要
import 'zod/compile'这个副作用导入即可(全局生效); - 它是可选优化:解析量大的场景(日志管道、批量导入、边界校验热点)收益明显;普通业务代码收益有限;
- 编译方案是有限制的(复杂/异步 schema 可能不被支持或退化)——上生产前务必用你自己的 schema 做基准测试,别只看这个 1.5x。
6. 工程建议:四件事各用在哪
| 能力 | 什么时候用 | 代价 |
|---|---|---|
zod/mini | 体积敏感的库/前端包 | API 更啰嗦,生态工具可能不认 |
~standard(Standard Schema) | 写库、写框架、想解耦具体校验库 | 只能用规范内的能力(如 validate) |
toJSONSchema() | 对接 OpenAPI / 表单引擎 / LLM 工具参数 | 语义有损(如 .int() 变成范围、默认更严格) |
自定义 check() / superRefine | 业务规则(唯一性、跨字段、复杂条件) | 逻辑进了 schema,注意可测性与报错文案 |
一条原则:schema 只放「数据的形状与基本约束」,重业务逻辑留在服务层。superRefine 里塞太多分支,等于把业务规则藏进了校验器——排查起来比读业务代码更痛。
关联
自测
zod/mini和常规zod的核心差别是什么?什么时候选它?- Standard Schema 的
~standard.validate与parse有什么不同? toJSONSchema()输出里,为什么.optional()的字段不在required里?为什么对象会带additionalProperties: false?.check()/.refine()/.superRefine()三者的关系是什么?它们会改变类型吗?zod/compile实测提速多少?为什么不能盲目上生产?