如何在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会基于指定字段快速匹配对应验证分支。
实现步骤
- 提取非
id字段的必填Schema,避免重复代码 - 分别定义「创建新项」和「更新现有项」的两种对象Schema
- 用
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
相关产品推荐
相关产品推荐

