跳到主要内容

创建日期: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
}

三个要注意的细节(都是从这份真实输出里读出来的):

  1. .optional() 不会进 required —— required 只有 name;这是校验语义的正确映射;
  2. 对象默认 additionalProperties: false —— 转成 JSON Schema 后更严格(这与运行时 z.object() 默认「剥离未知键」略有差别,跨系统时要知道);
  3. .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.issues push 任意 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 里塞太多分支,等于把业务规则藏进了校验器——排查起来比读业务代码更痛。

关联

自测

  1. zod/mini 和常规 zod 的核心差别是什么?什么时候选它?
  2. Standard Schema 的 ~standard.validateparse 有什么不同?
  3. toJSONSchema() 输出里,为什么 .optional() 的字段不在 required 里?为什么对象会带 additionalProperties: false
  4. .check() / .refine() / .superRefine() 三者的关系是什么?它们会改变类型吗?
  5. zod/compile 实测提速多少?为什么不能盲目上生产?