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>
*/
核心优势
- 简洁明确 - 一行代码解决动态键验证需求
- 精确控制 - 可自由定义键和值的验证规则
- 类型安全 - 自动生成 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,开发者可以高效实现健壮的类型安全验证逻辑。