迁移指南
本迁移指南旨在按照影响程度由高到低,列出 Zod 4 中的破坏性更改。要了解有关 Zod 4 性能提升和新功能的更多信息,请阅读 介绍文章。
Zod 的许多行为和 API 已变得更加直观和一致。本文档中描述的重大变更通常代表了 Zod 用户的主要生活质量改进。我强烈建议您仔细阅读本指南。
注意 — Zod 3 导出了一些未记录的准内部工具类型和函数,这些不被视为公共 API 的一部分。它们的更改未在此处记录。
非官方代码迁移工具 — 社区维护了一个从 Zod v3 升级至 v4 的代码转换工具 zod-v3-to-v4 可供使用。
错误定制
Zod 4 统一整合了错误定制的 API,统一使用单一的 error 参数。之前 Zod 的错误定制 API 分散且不一致,Zod 4 中已清理优化。
废弃 message
用 error 取代 message。虽然 message 参数仍受支持,但已废弃。
移除 invalid_type_error 和 required_error
invalid_type_error / required_error 参数被移除。它们几年前匆忙加入,作为比 errorMap 更简洁的错误定制方式,但存在诸多陷阱(不能与 errorMap 同时使用),且与 Zod 实际的问题代码不匹配(不存在 required 问题代码)。
这些情况现在可用新的 error 参数清晰表达。
移除 errorMap
该参数名改为 error。
错误映射函数现在也可以直接返回普通字符串(而非 {message: string} 对象)。它们也可以返回 undefined,指示 Zod 转交给链中下一个错误映射处理。
ZodError
更新了 issue 格式
issue 格式经过显著简化。
下面是 Zod 3 issue 类型和它们在 Zod 4 中对应类型的列表:
虽然某些 Zod 4 的 issue 类型被合并、移除或修改,但每个 issue 在结构上基本与 Zod 3 对应类型相似(多数情况下完全相同)。所有 issue 依然遵循与 Zod 3 相同的基础接口,因此大多数常见的错误处理逻辑无需修改。
错误映射优先级变化
错误映射的优先级调整得更一致。具体来说,传递给 .parse() 的错误映射 不再 高于 schema 级别的错误映射。
废弃 .format()
ZodError 的 .format() 方法已废弃。请改用顶层的 z.treeifyError() 函数。更多信息请参阅 错误格式化文档。
废弃 .flatten()
ZodError 的 .flatten() 方法同样废弃。请改用顶层的 z.treeifyError() 函数。更多信息请参阅 错误格式化文档。
移除 .formErrors
此 API 与 .flatten() 完全相同,历史遗留且未记录。
废弃 .addIssue() 和 .addIssues()
如有必要,请直接向 err.issues 数组推入新问题。
z.number()
不再支持无限值
POSITIVE_INFINITY 和 NEGATIVE_INFINITY 不再被视作 z.number() 的有效值。
.safe() 不再接受浮点数
在 Zod 3 中,z.number().safe() 已废弃。现在它的行为与 .int() 相同(参见下文),重要的是它不再接受浮点数。
.int() 只接受安全整数
z.number().int() 不再接受不安全整数(超出 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 范围)。使用超出该范围的整数会引发自动舍入错误。(此外,你应该改用 z.int()。)
z.string() 更新
废弃 .email() 等方法
字符串格式现在表示为 ZodString 的 子类,而非简单的内部 refinement。因此,这些 API 已移动到顶级 z 命名空间。顶级 API 也更简洁、更支持 Tree-shaking。
原先的方法形式(z.string().email())依旧存在且正常工作,但已被废弃。
更严格的 .uuid()
z.uuid() 现在根据 RFC 9562/4122 规范更严格地验证 UUID;具体来说,变体位必须是 10。对于更宽松的“类似 UUID”验证器,请使用 z.guid()。
.base64url() 不支持填充字符
z.base64url()(原 z.string().base64url())不再允许填充字符。通常 base64url 字符串应无填充且 URL 安全。
移除 z.string().ip()
此方法被拆分为 .ipv4() 和 .ipv6() 两个方法。若需支持两者,用 z.union() 合并。
更新 z.string().ipv6()
验证现在使用 new URL() 构造函数,比之前的正则表达式方法更稳健。之前通过验证的一些无效值现在可能失败。
移除 z.string().cidr()
类似地,拆分为 .cidrv4() 和 .cidrv6() 两个方法。若需同时支持,使用 z.union()。
z.coerce 更新
所有 z.coerce 模式的输入类型现在为 unknown。
.default() 更新
.default() 的行为有细微变化。若输入为 undefined,ZodDefault 会短路解析流程并返回默认值。默认值必须可赋给 输出类型。
在 Zod 3 中,.default() 期望值符合 输入类型,ZodDefault 会解析默认值,而非短路。因此默认值必须赋给 输入类型。
为重现旧行为,Zod 提供了 .prefault() 新 API,意为“预解析默认值”。
z.object()
在可选字段中应用的默认值
即使在可选字段中,您属性内的默认值也会被应用。这更符合预期,并解决了 Zod 3 中长期存在的可用性问题。这是一个微妙的变化,可能会导致依赖于键存在等的代码路径出现问题。
废弃 .strict() 和 .passthrough()
这些方法一般不再必需。请使用顶级的 z.strictObject() 和 z.looseObject() 函数代替。
这些方法仍可用以兼容旧代码,且不会被移除,被视为遗留 API。
废弃 .strip()
该方法本身价值不大,且是 z.object() 的默认行为。若需将严格对象转换为“普通”对象,使用 z.object(A.shape) 即可。
移除 .nonstrict()
此旧别名对应 .strip(),早已废弃,现在移除。
移除 .deepPartial()
在 Zod 3 中早已废弃,Zod 4 完全移除。无直接替代方案。其实现存在很多陷阱,且一般被视为反模式。
修改 z.unknown() 可选性
z.unknown() 和 z.any() 类型在推断类型中不再标记为“对象键可选”。
不推荐使用 .merge()
ZodObject 上的 .merge() 方法已被不推荐使用,建议使用 .extend()。.extend() 方法提供相同的功能,避免了严格性继承的模糊性,并且在 TypeScript 中性能更好。
注意:为了获得更好的 TypeScript 性能,考虑使用对象解构而不是 .extend()。有关更多详细信息,请参见 API 文档。
z.nativeEnum() 已弃用
z.nativeEnum() 函数现已弃用,建议使用 z.enum()。z.enum() API 已被重载以支持类似枚举的输入。
作为 ZodEnum 重构的一部分,一些长期废弃且冗余的特性被移除。这些都相同且仅出于历史原因存在。
z.array()
.nonempty() 类型变化
其行为现在与 z.array().min(1) 等同。推断类型不变。
旧行为现在更适合用 z.tuple() 和 “rest” 参数表达,和 TypeScript 类型系统更贴近。
废弃 z.promise()
极少需要使用 z.promise()。若输入可能是 Promise,请先 await 后再使用 Zod 解析。
若你用 z.promise 配合 z.function() 定义异步函数,也不再需要,详见下面的 ZodFunction 部分。
z.function()
z.function() 的结果不再是 Zod 模式,而是独立的“函数工厂”,用于定义经过 Zod 校验的函数。API 也有变化;你现在需提前定义 input 和 output schema,而非使用 .args() 和 .returns() 方法。
如果你迫切需要一个拥有函数类型的 Zod 模式,可以考虑此解决方案。
新增 .implementAsync()
定义异步函数时,使用 implementAsync() 替代 implement()。
.refine()
忽略类型谓词
在 Zod 3 中,作为校验函数的 类型谓词 可缩小模式类型。此行为未记录但曾有讨论。现已取消。
移除 ctx.path
Zod 新的解析架构不再主动计算 path 数组。此改动是 Zod 4 巨大性能提升的关键。
移除第二个参数为函数的重载
该令人困扰的重载已移除。
z.ostring() 等已删除
未记录的便利方法 z.ostring()、z.onumber() 等已被移除。这些是用于定义可选字符串模式的简写方法。
z.literal()
移除 symbol 支持
符号不是字面量值,且不能简单用 === 比较。这是 Zod 3 中的一个疏忽。
静态 .create() 工厂方法移除
之前所有 Zod 类定义了静态 .create() 方法。它们现在作为独立工厂函数实现。
z.record()
移除单参数用法
之前 z.record() 可以只传一个参数。现已不支持。
增强枚举支持
Record 类型更智能。Zod 3 中传入枚举作为 key 类型时,推断为部分类型:
Zod 4 中不再是部分类型。推断为预期类型,且 Zod 确保解析时所有枚举键均存在。
要使用可选键复制旧行为,请使用 z.partialRecord():
z.intersection()
合并冲突时抛出 Error
Zod 交叉类型先将输入解析为两个模式,然后尝试合并结果。Zod 3 中合并失败时,抛出带有特殊 "invalid_intersection_types" issue 的 ZodError。
Zod 4 中则抛出普通 Error。合并失败表明 schema 存在结构性问题:两个不兼容类型的交叉。因此,抛出普通错误比验证错误更合适。
内部更新
一般 Zod 用户可以忽略以下内容。这些更改不影响面向用户的 z API。
内部变动很多,无法全部列出,但对某些(有意或无意)依赖实现细节的用户可能相关。特别对基于 Zod 构建工具的库作者有用。
泛型更新
若干类的泛型结构发生变化,最显著的是 ZodType 基类:
第二个泛型 Def 被完全移除。基类仅跟踪 Output 与 Input。输入默认值由原来的 Output 变为 unknown,这让涉及 z.ZodType 的泛型函数更符合直觉。
不再需要 z.ZodTypeAny,使用 z.ZodType 即可。
新增 z.core 模块
为便于 Zod 与 Zod Mini 之间的代码共享,诸多实用函数与类型已迁移至新的 zod/v4/core 子包中。
For convenience, the contents of zod/v4/core are also re-exported from zod and zod/mini under the z.core namespace.
详见 Zod Core 文档了解核心子库内容。
移动 ._def
._def 属性现在移动到 ._zod.def。所有内部定义结构可能会变,主要针对库作者,无详尽说明。
移除 ZodEffects
这不影响用户 API,但值得强调,是 Zod 如何处理 refinements(细化校验)的重大内部重构。
之前,修饰和转换都存在于一个名为 ZodEffects 的包装类中。这意味着将任一项添加到模式中会将原始模式包装在一个 ZodEffects 实例中。在 Zod 4 中,修饰现在直接存在于模式内部。更准确地说,每个模式包含一个“检查”的数组;“检查”的概念在 Zod 4 中是新的,并且将修饰的概念推广到包括可能有副作用的转换,例如 z.toLowerCase()。
这在 Zod Mini API 中尤为明显,该 API 强烈依赖于 .check() 方法来组合各种验证。
新增 ZodTransform
同时,转换移入专门的 ZodTransform 类。此模式表示输入转换;实际上,你可以定义独立转换:
主要与 ZodPipe 配合使用。.transform() 方法返回 ZodPipe 实例。
移除 ZodPreprocess
与 .transform() 类似,z.preprocess() 现在返回 ZodPipe,不再是独立的 ZodPreprocess。
移除 ZodBranded
品牌化现在通过直接修改推断类型来处理,而不是使用专门的 ZodBranded 类。面向用户的 API 保持不变。

