教程 zod tutorial

递归类型

Zod 支持递归模式,这对于验证嵌套数据结构非常有用。

基本递归

使用 z.lazy() 创建递归模式:

ts
import { z } from "zod";

// 定义一个递归的类别结构
type Category = {
  name: string;
  subcategories: Category[];
};

const Category: z.ZodType<Category> = z.lazy(() =>
  z.object({
    name: z.string(),
    subcategories: z.array(Category),
  })
);

// 验证
Category.parse({
  name: "食品",
  subcategories: [
    {
      name: "水果",
      subcategories: [
        {
          name: "热带水果",
          subcategories: [],
        },
      ],
    },
  ],
}); // 通过

互递归类型

你可以定义多个相互递归的类型:

ts
// 文件系统示例
const FileSystem = z.lazy(() =>
  z.object({
    type: z.literal("directory"),
    children: z.array(z.union([File, FileSystem])),
  })
);

const File = z.object({
  type: z.literal("file"),
  content: z.string(),
});

// 验证
FileSystem.parse({
  type: "directory",
  children: [
    {
      type: "file",
      content: "hello.txt",
    },
    {
      type: "directory",
      children: [
        {
          type: "file",
          content: "world.txt",
        },
      ],
    },
  ],
}); // 通过

JSON 类型

使用递归类型定义 JSON 数据结构:

ts
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;

const JsonSchema: z.ZodType<Json> = z.lazy(() =>
  z.union([
    literalSchema,
    z.array(JsonSchema),
    z.record(JsonSchema),
  ])
);

type Json = Literal | { [key: string]: Json } | Json[];

// 验证
JsonSchema.parse({
  string: "hello",
  number: 42,
  array: [1, 2, 3],
  object: {
    nested: {
      data: [true, null, "world"],
    },
  },
}); // 通过

性能考虑

递归类型可能会影响验证性能,特别是对于深度嵌套的数据:

ts
// 添加最大深度限制
const LimitedCategory = z.lazy(() =>
  z.object({
    name: z.string(),
    subcategories: z.array(LimitedCategory).max(5), // 限制子类别数量
  })
);

// 添加最大递归深度
let depth = 0;
const MaxDepthCategory = z.lazy(() => {
  if (depth > 3) throw new Error("最大递归深度超出");
  depth++;
  return z.object({
    name: z.string(),
    subcategories: z.array(MaxDepthCategory),
  });
});

类型推断

TypeScript 可以正确推断递归类型:

ts
const Comment = z.lazy(() =>
  z.object({
    text: z.string(),
    replies: z.array(Comment),
  })
);

type Comment = z.infer<typeof Comment>;
// {
//   text: string;
//   replies: Comment[];
// }

// 使用类型
const comment: Comment = {
  text: "父评论",
  replies: [
    {
      text: "子评论",
      replies: [],
    },
  ],
};