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

如何在Zod中实现发票项基于id的条件验证?

Zod 实现发票项的条件验证规则

需求说明

在用Zod构建验证Schema时,items字段为发票项的对象数组,需满足以下验证逻辑:

  • 若发票项包含id字段(更新现有项):其余所有字段均为可选
  • 若发票项不包含id字段(创建新项):其余所有字段必须必填

原代码片段

items: z.array(z.object({
        id: z.number({required_error: "'id' is required", invalid_type_error: "'id' must be a number"}).positive("'id' must be a positive number").optional(),
        articleId: z.number({required_error: "'articleId' is required", invalid_type_error: "'articleId' must be a number"}).positive("'articleId' must be a positive number"),
        sku: z.number({required_error: "'sku' is required", invalid_type_error: "'sku' must be a number"}).positive("'sku' must be a positiv number"),
        description: z.string({ required_error: "'description' is required" }).nonempty("'description' cannot be empty").max(1000, { message: "'description' is too long" }),
        quantity: z.number({required_error: "'quantity' is required", invalid_type_error: "'quantity' must be a number"}).positive("'quantity' must be a positive number"),
        price: z.number({required_error: "'price' is required", invalid_type_error: "'price' must be a number"}).positive("'price' must be a positive number"),
        term: z.enum(["month", "year", "flat", "piece"], { required_error: "'term' is required" }),
    })),

解决方案

使用Zod的discriminatedUnion(区分联合类型)实现互斥条件验证,这种方式比普通union更高效,因为Zod会基于指定字段快速匹配对应验证分支。

实现步骤

  1. 提取非id字段的必填Schema,避免重复代码
  2. 分别定义「创建新项」和「更新现有项」的两种对象Schema
  3. 用discriminatedUnion组合两种Schema,再包裹为数组

修改后的完整代码

// 定义所有非id字段的必填Schema
const invoiceItemRequiredFields = z.object({
  articleId: z.number({
    required_error: "'articleId' is required",
    invalid_type_error: "'articleId' must be a number"
  }).positive("'articleId' must be a positive number"),
  sku: z.number({
    required_error: "'sku' is required",
    invalid_type_error: "'sku' must be a number"
  }).positive("'sku' must be a positive number"), // 修正原代码拼写错误
  description: z.string({ 
    required_error: "'description' is required" 
  }).nonempty("'description' cannot be empty").max(1000, { 
    message: "'description' is too long" 
  }),
  quantity: z.number({
    required_error: "'quantity' is required",
    invalid_type_error: "'quantity' must be a number"
  }).positive("'quantity' must be a positive number"),
  price: z.number({
    required_error: "'price' is required",
    invalid_type_error: "'price' must be a number"
  }).positive("'price' must be a positive number"),
  term: z.enum(["month", "year", "flat", "piece"], { 
    required_error: "'term' is required" 
  }),
});

// 创建新项:无id,所有字段必填
const createInvoiceItem = invoiceItemRequiredFields.extend({
  id: z.undefined() // 明确id字段不存在,也可省略id字段
});

// 更新现有项:有id,其余字段可选
const updateInvoiceItem = z.object({
  id: z.number({
    invalid_type_error: "'id' must be a number"
  }).positive("'id' must be a positive number")
}).merge(invoiceItemRequiredFields.partial()); // 将必填字段转为可选

// 最终Schema
const invoiceSchema = z.object({
  items: z.array(
    z.discriminatedUnion("id", [
      createInvoiceItem,
      updateInvoiceItem
    ])
  )
});

关键说明

  • discriminatedUnion指定id为区分字段,Zod会根据该字段是否存在自动匹配对应的验证规则
  • invoiceItemRequiredFields.partial()一键将所有必填字段转为可选,减少重复代码
  • createInvoiceItem中用z.undefined()明确限制id字段不能存在,比单纯省略更严谨

内容的提问来源于stack exchange,提问作者hantoren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:44:56