zod 模式验证库

Zod是一个TypeScript优先的模式验证库,它能够以简洁的链式API定义数据模式并对数据进行校验。Zod的设计目标是消除重复的类型声明,开发者只需定义一次验证器,Zod就能自动推断出对应的TypeScript类型。在实际项目中,Zod常用于表单验证、API请求和响应校验、环境变量验证等场景,Zod是目前NodeJS生态中最流行的模式验证库之一。

项目主页:https://zod.dev/

安装

执行以下命令使用npm安装Zod。

npm i zod

Zod是零依赖的轻量级库,其原生支持TypeScript,无需额外安装类型定义包。

基础用法

定义模式并验证数据

Zod的核心用法是定义模式(Schema)并用它来验证数据,下面是一个简单的例子。

import { z } from "zod";

// 定义一个字符串模式
const nameSchema = z.string();

// 验证数据
const result1 = nameSchema.parse("Tom");      // 返回`Tom`
const result2 = nameSchema.parse(123);        // 抛出`ZodError`异常

parse()方法用于验证数据,在验证成功时,它会返回根据模式自动推断的类型,在验证失败时则会抛出异常,如果希望不抛出异常而是返回验证结果对象,可以使用safeParse()方法。

const result = nameSchema.safeParse(123);

if (result.success) {
  console.log(result.data);  // 验证成功,获取数据
} else {
  console.log(result.error); // 验证失败,获取错误信息
}

基础类型模式

Zod提供了丰富的基础类型模式。

import { z } from "zod";

// 基础类型
z.string();      // 字符串
z.number();      // 数字
z.boolean();     // 布尔值
z.bigint();      // BigInt
z.date();        // Date对象
z.undefined();   // undefined
z.null();        // null
z.void();        // void
z.any();         // any类型
z.unknown();     // unknown类型

// 字面量类型
z.literal("hello");   // 只能是 "hello"
z.literal(42);        // 只能是 42

对象模式

除了基础类型模式,实际开发中最常用的是对象模式,它用于验证复杂的数据结构是否符合要求。

import { z } from "zod";

const userSchema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().email(),
});

const user = userSchema.parse({
  name: "Tom",
  age: 18,
  email: "tom@example.com",
});

这里parse()返回的user是一个自动推断的包含用户模式规定字段的类型,我们可以从其中取用nameage等字段的值。

对象模式提供了一些实用的方法来扩展或修改模式。

// 所有字段可选
const partialUserSchema = userSchema.partial();

// 只保留部分字段
const nameOnlySchema = userSchema.pick({ name: true });

// 排除部分字段
const withoutEmailSchema = userSchema.omit({ email: true });

// 扩展字段
const extendedUserSchema = userSchema.extend({
  role: z.string(),
});

// 合并两个对象模式
const mergedSchema = userSchema.merge(anotherSchema);

数组模式

数组模式用于验证数组类型的数据。

import { z } from "zod";

// 字符串数组
const stringArraySchema = z.array(z.string());

stringArraySchema.parse(["a", "b", "c"]);  // 通过
stringArraySchema.parse(["a", 1, "c"]);    // 失败

// 非空数组
const nonEmptyArraySchema = z.array(z.number()).nonempty();

// 限制数组长度
const limitedArraySchema = z.array(z.string()).min(1).max(10);

联合类型和可选类型

import { z } from "zod";

// 联合类型
const stringOrNumberSchema = z.union([z.string(), z.number()]);
// 简写形式
const stringOrNumberSchema2 = z.string().or(z.number());

// 可选类型
const optionalStringSchema = z.string().optional();  // string | undefined

// 可空类型
const nullableStringSchema = z.string().nullable();  // string | null

// 带默认值
const defaultStringSchema = z.string().default("default value");

枚举类型

import { z } from "zod";

// Zod枚举
const roleSchema = z.enum(["admin", "user", "guest"]);
type Role = z.infer<typeof roleSchema>;  // "admin" | "user" | "guest"

// 使用TypeScript原生枚举
enum Status {
  Active = "active",
  Inactive = "inactive",
}
const statusSchema = z.nativeEnum(Status);

自动推断TypeScript类型

Zod最强大的特性之一是能够从模式中自动推断TypeScript类型,避免重复声明。

import { z } from "zod";

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  age: z.number().optional(),
});

// 自动推断类型
type User = z.infer<typeof userSchema>;

// 等同于以下手动声明:
// type User = {
//   id: number;
//   name: string;
//   email: string;
//   age?: number;
// }

// 使用推断的类型
const createUser = (data: User) => {
  // ...
};

字符串验证方法

Zod为字符串类型提供了丰富的验证方法,下面是一些例子。

import { z } from "zod";

z.string().min(5);                    // 最小长度
z.string().max(100);                  // 最大长度
z.string().length(10);                // 精确长度
z.string().email();                   // 邮箱格式
z.string().url();                     // URL格式
z.string().uuid();                    // UUID格式
z.string().regex(/^[a-z]+$/);         // 正则匹配
z.string().startsWith("https://");    // 前缀匹配
z.string().endsWith(".com");          // 后缀匹配
z.string().trim();                    // 自动去除首尾空格
z.string().toLowerCase();             // 自动转小写
z.string().toUpperCase();             // 自动转大写

数字验证方法

对于数字Zod也提供了很多验证方法。

import { z } from "zod";

z.number().min(0);           // 最小值
z.number().max(100);         // 最大值
z.number().int();            // 整数
z.number().positive();       // 正数
z.number().negative();       // 负数
z.number().nonnegative();    // 非负数
z.number().nonpositive();    // 非正数
z.number().finite();         // 有限数
z.number().multipleOf(5);    // 5的倍数

自定义验证

当内置验证方法无法满足需求时,可以使用refine()方法添加自定义验证逻辑。

import { z } from "zod";

// 简单自定义验证
const passwordSchema = z.string().refine(
  (val) => val.length >= 8 && /[A-Z]/.test(val) && /[0-9]/.test(val),
  { message: "密码必须至少8位且包含大写字母和数字" }
);

// 跨字段验证
const formSchema = z.object({
  password: z.string(),
  confirmPassword: z.string(),
}).refine(
  (data) => data.password === data.confirmPassword,
  {
    message: "两次输入的密码不一致",
    path: ["confirmPassword"],  // 指定错误路径
  }
);

对于需要进行异步验证的场景(如检查用户名是否已存在),可以使用refineAsync()方法。

数据转换

Zod支持在验证的同时对数据进行转换,下面是一些例子。

import { z } from "zod";

// 使用transform转换数据
const numberStringSchema = z.string().transform((val) => parseInt(val, 10));
const result = numberStringSchema.parse("42");  // 返回数字类型的`42`

// 使用coerce进行类型强制转换
const coercedNumberSchema = z.coerce.number();
coercedNumberSchema.parse("42");    // 返回`42`
coercedNumberSchema.parse(true);    // 返回`1`

const coercedDateSchema = z.coerce.date();
coercedDateSchema.parse("2024-01-01");  // 返回`Date`对象

错误处理

Zod提供了详细的错误信息,便于定位验证失败的原因。

import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(2, "名称至少2个字符"),
  age: z.number().min(0, "年龄不能为负数"),
});

try {
  userSchema.parse({ name: "T", age: -1 });
} catch (error) {
  if (error instanceof z.ZodError) {
    console.log(error.issues);
    // [
    //   { code: 'too_small', message: '名称至少2个字符', path: ['name'], ... },
    //   { code: 'too_small', message: '年龄不能为负数', path: ['age'], ... }
    // ]

    // 格式化错误信息
    console.log(error.flatten());
    // { fieldErrors: { name: ['名称至少2个字符'], age: ['年龄不能为负数'] } }
  }
}

实际应用场景案例

API响应验证

在调用外部API时,使用Zod验证响应数据可以确保类型安全。

import { z } from "zod";

const apiResponseSchema = z.object({
  code: z.number(),
  data: z.object({
    users: z.array(z.object({
      id: z.number(),
      name: z.string(),
    })),
  }),
});

const fetchUsers = async () => {
  const response = await fetch("/api/users");
  const json = await response.json();
  return apiResponseSchema.parse(json);  // 验证并返回类型安全的数据
};

环境变量验证

使用Zod验证环境变量可以在应用启动时尽早发现配置问题。

import { z } from "zod";

const envSchema = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]),
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().default(3000),
});

export const env = envSchema.parse(process.env);
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。