DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
RackNerd;$21.99/年 · 1GB KVM
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Zod 校验已经失败,整理错误时又报读取 push 失败

Zod 4.4.3 的 formatError 和 treeifyError 遇到 toString 等路径可能再次抛错。区分校验结果与错误树构造,核对正式修正并保留动态键。

safeParse() 已经正常返回失败结果,接着调用 z.treeifyError() 或 z.formatError(),却又抛出:

TypeError: Cannot read properties of undefined (reading 'push')

如果错误路径中包含 toString、constructor 等与 Object 原型成员同名的键,检查是否使用 Zod 4.4.3 的错误格式化实现。这次异常发生在整理校验错误时,不表示最初的非法输入通过了校验。

错误树误读了继承属性

formatError 与 treeifyError 会把 issues 中的路径逐级放进对象。旧实现用真值或空值判断来决定某个节点是否存在,但普通对象会继承 toString、constructor 等属性。

读取这些名字时,代码可能得到继承的函数,误以为已有错误节点,随后访问并不存在的 errors 或 _errors 数组,最终在 push 处抛错。__proto__ 还涉及继承的 setter,不能只用一次普通赋值就认定创建了自有键。

动态字段、record 校验和自定义 addIssue 路径,都可能带来这样的名字。下面是将错误放到固定 toString 路径的示例:

import * as z from 'zod';

const schema = z.object({ data: z.string() }).superRefine((value, ctx) => {
  ctx.addIssue({
    code: 'custom',
    path: ['toString'],
    message: '该字段的业务规则未满足',
  });
});

const result = schema.safeParse({ data: 'hello' });
if (!result.success) {
  const tree = z.treeifyError(result.error);
  console.log(tree);
}

这个自定义规则故意产生一条错误,只为表达触发条件;并没有声称字符串 hello 本身不符合 z.string。

使用包含自有属性保护的正式版本

正式 Zod 4.6.7 的对应实现,会检查节点是否为自有属性,并用 defineProperty 建立相关节点,避免把继承成员当成错误容器。4.4.3 命中这一故障的项目,可以沿现有升级流程采用含该处理的正式版本,再核对实际加载的是哪份 Zod。

这里能确认 4.6.7 包含修正,不据此指定最早修复版本,也不把尚未进入所用发布物的补丁当成现成修复。切换依赖后,错误展示层若仍使用另一份旧副本,也需要一起对照。

暂时无法升级时,错误响应可以先按现有协议从 result.error.issues 读取路径与消息,避免再次调用有问题的树构造器。但这不是直接把完整 issue 对象对外输出的理由;服务端错误响应仍应只暴露协议允许的字段。

不要为隐藏格式化异常,删除所有与原型成员同名的错误路径。这会让用户看不到真正的问题字段。也不要修改全局 Object.prototype,或在第一次格式化失败后把校验结果改成成功。

与可选输入字段报错分开检查

同名键也可能在更早的对象解析阶段出现问题,例如没有传 constructor 字段,却被读取到继承函数。那是输入字段访问路径;此处是校验已失败后构造输出错误树。两处需要分别核对,修复格式化器不代表所有输入访问规则都改变了。

验收应覆盖普通字段、嵌套字段、toString 和 constructor 路径,并确认非法输入仍然被拒绝、错误消息归到正确节点。项目若允许任意动态键,还应检查这些键展示时的转义与响应结构,不能只确认 push 异常消失。