Zod 4.6
Zod 4.6 现已发布。
概览:
.validate()— 无需构造结果即可检查输入是否有效(已编译 schema 上比.safeParse().success最多快 35 倍)z.instanceof().properties()— 验证实例的属性fromJSONSchema()— 现在会执行此前忽略的六个验证关键字z.iban()— 电子格式 IBAN 与 mod-97 校验和- 塔吉克语语言包 —
z.locales.tg(),于 4.6.1 加入 z.withParser()— 在不支持new Function的环境中安装由其他方式生成的 parser- 更快的 CommonJS — 移除每个导出上的 getter(
require下的z.validate()约快 3 倍) - 更快地拒绝无效
z.url()— 使用URL.canParse()替代会抛错的构造器(4.6.4 中对无效输入约快 50 倍) - 递归 schema 的内存保留 — 释放已解析的输入,修复 4.5 中的内存耗尽回归
@zod/mini— Zod Mini 自 4.5 起作为独立包发布,并与zod同步版本
.validate()
Zod、Zod Mini 和 Zod Core 均支持独立的布尔值验证。它无需构造 ZodError,即可判断“这个输入是否有效?”,因此拒绝无效输入的成本很低。返回类型是针对 schema 输入类型的类型守卫。
Zod Classic schema 也提供此方法。(#6547)
结合 z.compile() 使用时,对无效输入的验证速度比 .safeParse().success 最多快 35 倍。
两张图都测量失败路径。.validate() 的关键优势是遇到第一个 issue 时即可提前返回,不必汇总完整的 ZodIssue[] 数组。
注意 — 异步 refinement 可使用 .validateAsync()。
z.properties()
z.instanceof() 新增了 .properties() 方法,用于验证实例的指定属性。与 z.object() 不同,它会在原对象上进行验证,因此适用于 class 实例。(#6536)
输入会原样返回,因此原型得以保留,实例方法也能继续使用。这是 z.object() 做不到的:它会返回普通对象,原来的 Response 实例也就不复存在。
fromJSONSchema()
z.fromJSONSchema() 现在还支持六个 JSON Schema 关键字。(#6535)
两个属性数量边界都会统计输入对象自身的键。数组唯一性按结构比较,因此 [{ a: 1 }, { a: 1 }] 会被视为重复项。
z.iban()
新增的字符串格式:电子格式的 IBAN,并通过有效的 ISO 7064 MOD 97-10 校验和验证。(#6571)
z.withParser()
z.compile() 使用 new Function 构建 parser,严格的 Content Security Policy 会阻止此操作。z.withParser() 单独提供了安装器:它接收由其他方式(例如构建时工具或原生编译器)生成的 parser,并按相同约定安装。(#6575)
所提供的 parser 负责生成完整结果,因此必须返回与 schema 相同的结果。此示例会重新构建对象,而不是直接返回输入,因为 z.object() 会移除未知键。返回 z.INVALID 则会把输入交给 runtime 处理;runtime 仍是生成 ZodError 的唯一来源。
塔吉克语语言包
塔吉克语(tg)在 4.6.1 中加入语言包列表。(#6581,贡献者 @ismoil77)
更快的 CommonJS
TypeScript 会将重新导出编译为 getter;Zod 4.5 的 CommonJS 入口中,255 个导出有 252 个是 getter。V8 无法透过 getter 识别出固定的调用目标,因此不能内联调用。4.6 构建会生成普通属性,并冻结导出对象。对于已编译的 schema,在 require 下调用 z.validate() 比 Zod 4.5 快约 3 倍。(#6564)
只有通过命名空间调用的方式受到影响。Player.safeParse(data) 这样的实例方法调用不会读取导出对象,ESM 构建也没有变化。
更快地拒绝无效 z.url()
从 4.6.4 起,单独使用 z.url() 时会通过 URL.canParse() 验证,而不再调用会抛出异常的构造器,因此拒绝无效 URL 约需 90 ns,原先约需 4.6 µs。使用 hostname、protocol 或 normalize 选项时会调用 URL.parse();它会返回 null 而不是抛出异常,但处理有效的规范化 URL 会慢约 5%。(#6588)
递归 schema 的内存保留
递归 schema 会一直持有上一次解析的输入和输出,直到下一次解析将其替换,因此一个长期存活的 schema 会固定住它处理过的所有对象。Zod 4.4 会释放该输入,4.5 则不会;这导致一次全仓库 lint 运行因内存耗尽而失败。现在解析状态中的引用都改为弱引用:解析一次包含 2.9 万个节点的树只保留 2.2 MB,之前为 10.1 MB;递归解析的耗时则增加约 6%。(#6572)
错误修复
⚠️ 首次读取 error 时才会运行错误映射
由于 safeParse() 现在会延迟构造错误,全局、语言包以及 schema 上的 error 错误映射都会在首次读取 result.error 时运行,而不是在解析时运行。如果代码在解析后、读取错误前更换了 z.config(),则会使用较新的配置。(#6519)
如果没有读取错误,带副作用的错误映射就不会运行。抛出式解析不受影响:.parse() 会立即构造并抛出错误,不会走延迟路径,堆栈仍会指向调用位置。
⚠️ z.emoji() 会拒绝只包含 emoji 组件的字符串
Unicode 的 Emoji_Component 属性涵盖了可附加到 emoji 上的组成部分,因此 z.emoji() 过去会接受 "123"、"#"、"*",以及单独出现的零宽连接符、变体选择符或肤色修饰符。现在的模式要求至少包含一个象形文字、区域指示符或键帽符号。(#6532)
旗帜、旗帜子区域、带肤色修饰符的 emoji 和 ZWJ 序列均不受影响。关闭 #6515。
⚠️ 数字 enum 的选项不再包含反向映射
数字类型的 TypeScript enum 在运行时还包含反向映射(例如 0 对应 "UK")。解析器此前会忽略这些键,但 .options 直接从 enum 对象读取,因此含三个成员的 enum 会列出六个值,其中三个无法通过解析。(#6542)
⚠️ base64 模式
z.base64() 和 z.base64url() 的运行时模式现在只检查字符集合,长度和填充则由代码验证,因此多 MB 字符串通过组合 schema 解析时不再会导致正则表达式调用栈溢出。JSON Schema 输出仍会生成精确的分块模式,所以 z.toJSONSchema() 的结果不变。(#6534,#6527)
将 z.base64() 组合进模板字符串时,现在只会检查字符集合,不检查长度;z.creditCard() 在此处也采用相同规则。导出的 z.regexes.base64url 现在是带长度限制的模式,因此面对多 MB 输入时也会像 z.regexes.base64 一样溢出。
⚠️ email 模式移除了前瞻断言
z.email() 原先以两个前瞻断言开头,第二个会在匹配开始前扫描整个字符串。现在两者都已移除,它们原本负责的规则(local part 中不能有空片段)改为通过结构表达,因此 z.email() 接受和拒绝的输入与之前完全相同。有效地址的验证速度约快一倍。(#6573)
模式字符串是用户可见的,因此它的每个副本都会变化:现在 z.regexes.email 不含捕获组;它过去暴露的两个捕获组都没有可用值。失败的 z.email() 产生的 issue.pattern 以及 z.toJSONSchema() 输出的 pattern 也会变化。后者不再含前瞻断言,因此 ECMAScript 之外的验证器也能编译它。
将 email 组合进模板字符串时,也不再把“不能有连续句点”的规则应用到 email 片段以外的字符串。
⚠️ 链式检查不再互相覆盖 JSON Schema 中的约束
过去每添加一项检查,就会按链式调用顺序将其边界写入 schema,因此在 .min() 和 .max() 后添加格式检查时,较严格的值会被格式检查自身的范围覆盖。现在转换器会将这些检查折叠为合取条件,链式调用的顺序不再影响输出。(#6554,#6553)
所有版本的运行时解析都会执行这些边界,错误只出在生成的 schema 上。同一折叠逻辑还修复了另外两种情况:重复调用 multipleOf 时只保留第一个除数、丢弃其余除数,因此 z.number().multipleOf(2).multipleOf(3) 生成的 schema 会接受 4;z.string().min(8).length(5) 则会生成 minLength: 5,放宽运行时仍会拒绝的边界。关闭 #6550。
⚠️ 元数据成员在首次读取时才会实例化
Zod Classic schema 的八个成员(.format、.minLength、.maxLength、.minValue、.maxValue、.isInt、.minDate 和 .maxDate)现在会根据各项检查计算,而不是在构造时写入每个实例。它们都是原型上的 getter,并会在首次读取时变为实例自身的属性。(#6554)
在读取前,这些键不会出现在实例上;Object.assign({}, schema) 也只会复制已经读取过的成员。删除某个成员会恢复其 getter,下次读取时会重新计算。
这些值也可能变化,因为 getter 使用的折叠逻辑与 JSON Schema 转换器相同。现在,顺序相关的检查链会报告更严格的边界,而不是最后写入的那一项。
提交记录
Zod 4.6 汇集了 72 个提交。
661673aedocs: make the 9thCO logo visible on the light theme by @colinhacks6de10dcedocs: reconcile the sponsor listings against every active sponsorship (#6579) by @colinhacks213ee75dfeat(compile): add z.withParser for externally generated parsers (#6575) by @colinhacksf9465d4edocs: reconcile the sponsor listings with active sponsorships (#6576) by @colinhacksf7fd5548perf(v4): drop the lookaheads from the email regex (#6573) by @colinhacks36f17960fix(v4): stop the memoizer from pinning a finished parse (#6572) by @colinhacks22bed613feat(v4): add z.iban() string format with mod-97 checksum (#6571) by @colinhacksc5b9bcb3bench: measure what a runtime island's leaked indent cost the generated source by @colinhackse54716cbdocs(ecosystem): add @apical-ts/craft (#5946) by @gunzipdcbcf052fix(compile): unwind the doc indent when a child generator throws (#6570) by @colinhacks277613a6docs: move the release procedure to the maintainer-local notes by @colinhackseb1c1089ci: release only on workflow_dispatch behind the npm environment (#6569) by @colinhacks741981ffperf(compile): for-in record walk, cheaper issue finalization, and a generative compile differential (#6567) by @colinhacks804e0f52perf: seal the CommonJS exports so require("zod") stops reading through a getter (#6564) by @colinhacks6f048367fix(v4): derive JSON Schema constraints by folding checks in the converter (#6554) by @colinhackse4d67f3eMigrate development and CI to Nub (#6562) by @colinhacks7a002366fix(v4): don't let format checks overwrite tighter min/max bounds (#6553) by @colinhacks5489a532test(v4): pin the check-chain case that keeps compiled validate's definite guard (#6551) by @colinhackse7604717docs: attribute the compiled failure cost to the fallback, not the double pass by @colinhacks764ac59fperf(v4): settle z.validate on the first failure in parse order (#6544) by @colinhacks07917f4ctest(v4): pin the lazy safeParse error's stack behavior (#6548) by @colinhacks62e6624bfeat(v4): add .validate() and .validateAsync() to Zod Classic (#6547) by @colinhackscafbee47fix(v4): parse recursive schemas built by a factory (#6530) by @colinhacks4d730882Release the parsed input once a failing safeParse builds its error (#6543) by @colinhacks90269c60Keep a numeric TS enum's reverse-mapping keys out of.options(#6542) by @colinhacks18e71c71Rename the JSON Schemaprocesshelper so bundler polyfills cannot collide (#6541) by @colinhacks68aca3dcdocs: cover the 4.5 API surface that never made it into the reference by @colinhackseca96871fix(v4): enforce the six JSON Schema keywords fromJSONSchema silently dropped (#6535) by @colinhacks81ded991perf: answer z.validate from the compiled fast path on invalid input (#6538) by @colinhacks51caf010refactor: collapse cachedInternal back into cached (#6540) by @colinhacksabfb3897feat(v4): make z.properties() a schema, and give z.instanceof() a .properties() method (#6536) by @colinhacks69f2a7ffCollapse toZod's normalizer and move its docs to the API reference (#6539) by @colinhacksbf990216perf: move util.cached's accessor to a prototype (#6537) by @colinhacksbec73beaperf(v4): build the safeParse error on first read (#6519) by @colinhacks07c43e2aKeep the runtime base64 regexes linear so composed parse paths cannot overflow (#6534) by @colinhacksbc1157e7docs: use a Response example for z.properties() by @colinhacks2ec972ecrefactor: collapse toZod's enum leaf normalizer to a dummy union (#6533) by @colinhacks68a609acWiden literal inputs in property check types (#6520) by @colinhacks0227e53ddocs: bump the star pill's GitHub mark to 20px by @colinhacks84dd3b0fperf: build literal and enum pattern regexes lazily (#6531) by @colinhacksf83ab511fix(v4): reject component-only strings from z.emoji() (#6532) by @colinhacks74f9a6d3docs: drop the toZod enum block from basics and pin the page's curation rule in a comment by @colinhacksa2a019a5Accept enum-typed targets in z.toZod (#6528) by @colinhacks319f47f4Emit a length-aware base64url pattern in toJSONSchema (#6527) by @colinhacks08ba069eperf(v4): read Luhn digits with charCodeAt instead of string indexing (#6529) by @colinhacks1ec6b7c5docs: add an RSS feed to the blog at /blog/rss.xml by @colinhacksb801439bbench: add typebox (compiled and dynamic) to the moltar cross-library harness by @colinhacks7ae49d64docs: drop the circle around the star pill's GitHub mark and center it on the pill's arc by @colinhacks93f3ab32docs: replace the blog navbar's GitHub icon with a star-count pill by @colinhacksfb2fedfddocs: tighten the memory chart callout, pad the canvas, say "less memory" by @colinhacksff56a551docs: center the memory chart callout labels and pad them off the number by @colinhacks8cd1250fdocs: center the memory chart callout labels by @colinhacks3195ed01docs: label the memory chart like the compile chart by @colinhacksa6b49390Mark the compile internals @internal instead of hiding them (#6518) by @colinhacks40b4d0b3fix(ci): read zod's latest version with npm view when picking the backfill dist-tag by @colinhacks5ff95665Stop re-exporting the compile internals from zod/v4/core (#6511) by @colinhacksf412178dci: publish @zod/mini to JSR in lockstep with npm (#6510) by @colinhacksf3e7c72efix(docs): render the docs 404 page inside the (doc) layout once by @colinhacksf3cb3644docs: surface the blog on the home page and in the sidebar by @colinhackscd4f9a67perf(v4): report Standard Schema issues without constructing a ZodError (#6509) by @colinhacks43b9bfc5docs: drop the bound-methods section from the Zod package page by @colinhacks70eb2c07docs: drop the traits section and the compilation feature bullet by @colinhacks1c0bce0cdocs: bring the 4.5 charts and worked examples into the docs pages by @colinhacksa0898b4bci: wait hours for npm to serve a publish, not ten minutes (#6502) by @colinhacksc46eeff0chore: narrow blanket biome-ignore comments (#6504) by @pullfrog[bot]c7ec94d3ci: check zod and @zod/mini lockstep on npm after every publish (#6507) by @colinhacks81065739chore(docs): build with Turbopack by @colinhacksabd41adbdocs(wiki): move plans and comparisons into a gitignored internal/ (#6506) by @colinhacks2956c4c2chore(mini): sync @zod/mini to 4.5.4 by @colinhacks8ce9e8d5feat(mini): publish Zod Mini as the standalone @zod/mini package (#6491) by @colinhacks93186cabdocs(wiki): drop the zod-compiler benchmark (#6505) by @colinhacks908c9e17fix(docs): retry the GitHub stars fetch and log the real status by @colinhacks