Skip to content

Zod 动态键对象

问题描述

在 TypeScript 中,我们常用 Record<string, string> 定义包含动态键的对象类型。但在 Zod 验证库中,初学者常尝试类似语法创建动态键验证:

typescript
// 这是错误的写法!
const data = z.object({
  [z.string()]: z.string(),
});

这种方法会失败,因为 Zod 的 object() 方法需要明确已知的键名。我们需要实现的功能是:

  • 验证对象键名必须为字符串类型
  • 验证所有键对应的值必须为字符串类型
  • 允许对象包含任意数量的键值对

最佳解决方案

Zod 提供 z.record() 方法专用于处理动态键对象场景:

typescript
const dynamicSchema = z.record(z.string(), z.string());

使用示例

typescript
import { z } from "zod";

// 1. 定义动态键验证模式
const userMetadataSchema = z.record(z.string(), z.string());

// 2. 验证数据
const validData = { 
  theme: "dark", 
  language: "zh-CN" 
};
userMetadataSchema.parse(validData); // 成功

// 3. 错误处理示例
const invalidData = { version: 1.0 };
try {
  userMetadataSchema.parse(invalidData);
} catch (error) {
  console.log(error.errors); 
  // 输出: [ { message: 'Expected string, received number', ... } ]
}

// 4. 类型推断
type UserMetadata = z.infer<typeof userMetadataSchema>;
/* 相当于:
type UserMetadata = Record<string, string>
*/

核心优势

  1. 简洁明确 - 一行代码解决动态键验证需求
  2. 精确控制 - 可自由定义键和值的验证规则
  3. 类型安全 - 自动生成 TypeScript 类型声明

高级用法

允许键名为特定格式(通过 .regex() 约束):

typescript
const emailMapSchema = z.record(
  z.string().email(), // 键必须是邮箱格式
  z.number()          // 值必须是数字
);

混合静态和动态键(使用 .catchall()):

typescript
const hybridSchema = z.object({
  id: z.number(),
  name: z.string(),
}).catchall(z.string()); // 额外字段必须为字符串

关键说明

参数意义

typescript
z.record(keySchema, valueSchema);
  • keySchema:键名验证规则(通常为 z.string()
  • valueSchema:值验证规则(任意 Zod 类型)

常见误区

  • 错误尝试 z.object() 的 "动态键" 写法
  • 键名验证规则不符合业务需求(如缺少正则约束)
  • 忘记处理解析错误(应用 try/catch.safeParse()

兼容性建议

使用 z.record() 时需考虑实际场景需求:

  • 如需完整限制键名类型 → 使用 .regex() 添加正则约束
  • 当需要同时存在静态/动态键 → 采用 .catchall() 方案
  • 当值的类型不一致时 → 改用 z.map() 或复合类型

注意事项

Zod 的 record() 在运行时验证所有键值对。超大型对象(如 10K+ 属性)可能影响性能,如有此类场景请考虑分区验证。

通过官方 z.record() API,开发者可以高效实现健壮的类型安全验证逻辑。