如何使用Zod条件验证与type数组对应的data数组元素?
Zod 实现数组对应位置的条件验证
需求说明
给定请求体中,type数组与data数组元素一一对应,需按以下规则验证:
type中为url的元素,对应data元素必须符合URL格式type中为email的元素,对应data元素必须符合邮箱格式type中为other的元素,对应data元素只需是字符串类型
实现思路
单独对data数组做验证无法关联对应位置的type值,必须在整个对象层面通过refine或superRefine做关联校验,同时支持自定义错误信息和错误定位。
完整代码实现
1. 基础验证方案(统一错误提示)
import { z } from "zod"; // 预定义各类型验证器,自带自定义错误信息 const urlValidator = z.string().url({ message: "INVALID_URL_FORMAT" }); const emailValidator = z.string().email({ message: "INVALID_EMAIL_FORMAT" }); const otherValidator = z.string({ message: "MUST_BE_STRING" }); const requestSchema = z.object({ type: z.array( z.enum(["url", "email", "other"], { errorMap: () => ({ message: "CANNOT_BE_EMPTY" }) }) ), data: z.array(z.string()), }).refine((obj) => { // 先校验两个数组长度一致 if (obj.type.length !== obj.data.length) return false; // 遍历对应位置元素做验证 for (let i = 0; i < obj.type.length; i++) { const type = obj.type[i]; const dataItem = obj.data[i]; let isValid = true; switch (type) { case "url": isValid = urlValidator.safeParse(dataItem).success; break; case "email": isValid = emailValidator.safeParse(dataItem).success; break; case "other": isValid = otherValidator.safeParse(dataItem).success; break; } if (!isValid) return false; } return true; }, { message: "DATA_MISMATCH_WITH_CORRESPONDING_TYPE", path: ["data"], });
2. 细化错误方案(定位到具体元素)
如果需要给每个错误位置单独提示并精确定位,可以用superRefine实现:
import { z } from "zod"; const urlValidator = z.string().url({ message: "INVALID_URL_FORMAT" }); const emailValidator = z.string().email({ message: "INVALID_EMAIL_FORMAT" }); const otherValidator = z.string({ message: "MUST_BE_STRING" }); const requestSchema = z.object({ type: z.array( z.enum(["url", "email", "other"], { errorMap: () => ({ message: "CANNOT_BE_EMPTY" }) }) ), data: z.array(z.string()), }).superRefine((obj, ctx) => { // 校验数组长度一致性 if (obj.type.length !== obj.data.length) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TYPE_AND_DATA_ARRAY_LENGTH_NOT_MATCH", path: ["data"], }); return; } // 逐个验证对应位置的元素 for (let i = 0; i < obj.type.length; i++) { const type = obj.type[i]; const dataItem = obj.data[i]; switch (type) { case "url": const urlResult = urlValidator.safeParse(dataItem); if (!urlResult.success) { ctx.addIssue({ ...urlResult.error.issues[0], path: ["data", i], // 错误定位到data数组的第i个元素 }); } break; case "email": const emailResult = emailValidator.safeParse(dataItem); if (!emailResult.success) { ctx.addIssue({ ...emailResult.error.issues[0], path: ["data", i], }); } break; case "other": const otherResult = otherValidator.safeParse(dataItem); if (!otherResult.success) { ctx.addIssue({ ...otherResult.error.issues[0], path: ["data", i], }); } break; } } });
关键说明
- 必须在对象层面做关联验证,因为单独验证
data元素时无法获取对应位置的type值 - 使用
superRefine可以更灵活地添加多个错误,并且精确定位到数组的具体索引位置 - 预定义的验证器可以复用,同时自带自定义错误信息,避免重复代码
内容的提问来源于stack exchange,提问作者charnould
相关产品推荐
相关产品推荐

