项目概况

🚀 3分钟秒懂: Java开发者视角下的Zod 框架

2026-06-243 min read前端技术
agent

🚀 3分钟秒懂:Java 视角下的 Zod

把 Zod 理解为:DTO 结构 + Bean Validation + TS 类型声明 的三合一工具。它不是 ORM,只是个运行时数据校验器


1. 概念对齐(Java vs Zod)

Java 概念Zod 对应写法说明
定义 DTO 类z.object({...})结构与规则写在一起
@NotBlank / @Sizez.string().min(1)基础格式校验
@Pattern(regexp=...)z.string().regex(...)正则校验
@Positivez.number().positive()数值校验
手动触发校验(抛异常)schema.parse(data)失败直接 throw Exception
校验并获取结果对象schema.safeParse(data)返回 { success: true/false } 不抛异常

⚠️ 核心注意点:Zod 只是规则定义,不会自动拦截!必须显式调用 parse()safeParse() 才会触发校验。


2. 核心大杀器:同步 vs 异步

  • 同步校验 (parse / safeParse):只检查格式(非空、数字、长度、邮箱)。
  • 异步校验 (parseAsync / safeParseAsync):用于检查业务。允许在校验时查数据库、调接口(例如:校验“银行账号是否存在”、“会计期间是否已关闭”)。

3. 高级进阶:复杂业务与类型联动

  • refine / superRefine**:相当于 Java 的自定义校验器(ConstraintValidator)**。适合写财务里的复杂逻辑(如:校验借贷平衡、单据总金额对不对)。
  • z.infer(前端降维打击)
typescript
1type Voucher = z.infer<typeof voucherSchema>; 2

单一真理源:写一份 Schema,自动反推出 TypeScript 的类型声明,再也不用重复写一遍 DTO 类了。


🛠️ 常用 API 备忘表

类别Zod APIJava 对应概念
容器z.object({...})public class MyDto { ... }
z.array(z.string())List<String>
基础z.string().min(1)@NotBlank
z.number().positive()@Positive
z.boolean()Boolean
z.enum(["CNY", "USD"])enum Currency { CNY, USD }
修饰z.string().optional()允许为 undefined (类似 null)

💻 极简 Demo 演示

以财务系统里最经典的“凭证录入(Voucher)”为例,包含结构定义、基础校验、自定义借贷平衡校验(Refine)以及类型反推:

typescript
1import { z } from 'zod'; 2 3// 1. 定义 Schema(相当于写 Java DTO + 校验注解) 4const voucherSchema = z.object({ 5 id: z.string().uuid(), // UUID 格式 6 period: z.string().regex(/^\d{4}-\d{2}$/), // 格式如 "2026-06" 7 currency: z.enum(["CNY", "USD"]), // 枚举限制 8 // 分录列表(相当于 List<Entry>),至少 2 条分录 9 entries: z.array( 10 z.object({ 11 accountCode: z.string().min(1), // 科目非空 12 debit: z.number().nonnegative(), // 借方金额 >= 0 13 credit: z.number().nonnegative(), // 贷方金额 >= 0 14 }) 15 ).min(2) 16}).superRefine((data, ctx) => { 17 // 复杂的业务校验:借贷平衡(类似 Java 的自定义 ConstraintValidator) 18 const totalDebit = data.entries.reduce((sum, e) => sum + e.debit, 0); 19 const totalCredit = data.entries.reduce((sum, e) => sum + e.credit, 0); 20 21 if (totalDebit !== totalCredit) { 22 ctx.addIssue({ 23 code: z.ZodIssueCode.custom, 24 message: `借贷不平衡!借方:${totalDebit}, 贷方:${totalCredit}`, 25 path: ['entries'] // 错误挂在 entries 字段上 26 }); 27 } 28}); 29 30// 2. 🪄 降维打击:一键反推出 TypeScript 类型(不需要手写 interface 啦!) 31type Voucher = z.infer<typeof voucherSchema>; 32 33 34// 3. 运行期测试数据 35const badData = { 36 id: "not-a-uuid", 37 period: "2026/06", 38 currency: "HKD", // 不在枚举内 39 entries: [ 40 { accountCode: "1001", debit: 100, credit: 0 }, 41 { accountCode: "2001", debit: 0, credit: 90 } // 借贷不平衡 42 ] 43}; 44 45// 4. 执行校验 46// 方案 A:直接抛异常(类似 Spring 默认行为) 47// voucherSchema.parse(badData); 48 49// 方案 B:优雅获取结果对象(适合 CLI 或业务层返回 Result) 50const result = voucherSchema.safeParse(badData); 51 52if (!result.success) { 53 // 打印格式化后的错误信息 54 console.log("❌ 校验失败:", result.error.format()); 55} else { 56 // 校验成功后,data 的类型自动安全推导为 Voucher 57 const validData: Voucher = result.data; 58 console.log("✅ 校验通过:", validData); 59}