基于数组字段值的Zod条件校验优化方案咨询
Zod条件校验请求体的简洁实现方案
针对你在Express中基于channels数组字段条件校验请求体的需求,以下是几种更简洁的优化方案:
方案一:整合校验逻辑到单个Schema(推荐)
将基础字段和所有渠道字段合并为一个Schema,通过refine方法根据channels内容校验对应字段是否完整,只需一次校验调用,避免冗余。
const ChannelEnum = z.enum(["EMAIL", "SMS", "FEED"]); // 基础必填字段 const baseSchema = z.object({ userId: z.string(), channels: z.array(ChannelEnum).nonempty(), }); // 各渠道对应的字段Schema const emailSchema = z.object({ email: z.string().nonempty().email(), firstName: z.string().nonempty(), lastName: z.string().nonempty(), emailSubject: z.string().nonempty(), emailText: z.string().nonempty(), emailHtml: z.string().nonempty(), }); const smsSchema = z.object({ phone: z.string().nonempty(), smsMessage: z.string().nonempty(), }); const feedSchema = z.object({ feedTitle: z.string().nonempty(), feedMessage: z.string().nonempty(), }); // 合并所有字段为可选,再通过refine强制条件必填 const requestSchema = baseSchema .merge(emailSchema.partial()) .merge(smsSchema.partial()) .merge(feedSchema.partial()) .refine( (data) => { // 按需校验对应渠道的字段完整性 if (data.channels.includes("EMAIL") && !emailSchema.safeParse(data).success) return false; if (data.channels.includes("SMS") && !smsSchema.safeParse(data).success) return false; if (data.channels.includes("FEED") && !feedSchema.safeParse(data).success) return false; return true; }, { message: "选中渠道的必填字段未填写完整", path: ["channels"], // 错误关联到channels字段 } ); // 单次校验请求体 const validationResult = requestSchema.safeParse(req.body); if (!validationResult.success) { const { message } = fromZodError(validationResult.error); return res.status(400).json({ error: { message } }); } // 使用校验后的合法数据 const validatedData = validationResult.data;
优势:代码紧凑,只需一次校验,逻辑集中,避免两次safeParse的冗余操作。
方案二:动态合并渠道Schema
用对象存储各渠道的Schema,通过reduce动态合并到基础Schema中,替代原有的多个if判断,让逻辑更清晰。
const ChannelEnum = z.enum(["EMAIL", "SMS", "FEED"]); const baseSchema = z.object({ userId: z.string(), channels: z.array(ChannelEnum).nonempty(), }); // 用映射表存储渠道与对应Schema的关系 const channelSchemaMap = { EMAIL: z.object({ email: z.string().nonempty().email(), firstName: z.string().nonempty(), lastName: z.string().nonempty(), emailSubject: z.string().nonempty(), emailText: z.string().nonempty(), emailHtml: z.string().nonempty(), }), SMS: z.object({ phone: z.string().nonempty(), smsMessage: z.string().nonempty(), }), FEED: z.object({ feedTitle: z.string().nonempty(), feedMessage: z.string().nonempty(), }), }; // 先校验基础字段 const baseValidation = baseSchema.safeParse(req.body); if (!baseValidation.success) { const { message } = fromZodError(baseValidation.error); return res.status(400).json({ error: { message } }); } // 动态合并选中渠道的Schema const combinedSchema = baseValidation.data.channels.reduce( (schema, channel) => schema.merge(channelSchemaMap[channel]), baseSchema ); // 最终校验完整数据 const finalValidation = combinedSchema.safeParse(req.body); if (!finalValidation.success) { const { message } = fromZodError(finalValidation.error); return res.status(400).json({ error: { message } }); } const validatedData = finalValidation.data;
优势:通过映射表和reduce简化了条件合并逻辑,比原有的多个if判断更简洁,易维护。
方案三:superRefine实现精准错误提示
如果需要给每个缺失字段单独返回错误信息,可以使用superRefine直接校验每个字段,错误提示更精准。
const ChannelEnum = z.enum(["EMAIL", "SMS", "FEED"]); const requestSchema = z.object({ userId: z.string(), channels: z.array(ChannelEnum).nonempty(), // 所有渠道字段先设为可选 email: z.string().nonempty().email().optional(), firstName: z.string().nonempty().optional(), lastName: z.string().nonempty().optional(), emailSubject: z.string().nonempty().optional(), emailText: z.string().nonempty().optional(), emailHtml: z.string().nonempty().optional(), phone: z.string().nonempty().optional(), smsMessage: z.string().nonempty().optional(), feedTitle: z.string().nonempty().optional(), feedMessage: z.string().nonempty().optional(), }).superRefine((data, ctx) => { // 校验EMAIL渠道字段 if (data.channels.includes("EMAIL")) { !data.email && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "email为必填项", path: ["email"] }); !data.firstName && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "firstName为必填项", path: ["firstName"] }); !data.lastName && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "lastName为必填项", path: ["lastName"] }); !data.emailSubject && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "emailSubject为必填项", path: ["emailSubject"] }); !data.emailText && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "emailText为必填项", path: ["emailText"] }); !data.emailHtml && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "emailHtml为必填项", path: ["emailHtml"] }); } // 校验SMS渠道字段 if (data.channels.includes("SMS")) { !data.phone && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "phone为必填项", path: ["phone"] }); !data.smsMessage && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "smsMessage为必填项", path: ["smsMessage"] }); } // 校验FEED渠道字段 if (data.channels.includes("FEED")) { !data.feedTitle && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "feedTitle为必填项", path: ["feedTitle"] }); !data.feedMessage && ctx.addIssue({ code: z.ZodIssueCode.custom, message: "feedMessage为必填项", path: ["feedMessage"] }); } }); const validationResult = requestSchema.safeParse(req.body); if (!validationResult.success) { const { message } = fromZodError(validationResult.error); return res.status(400).json({ error: { message } }); } const validatedData = validationResult.data;
优势:错误提示精准到具体字段,用户能明确知道哪个字段缺失或不符合要求,适合对错误信息要求高的场景。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

