You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于数组字段值的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 06:45:00