Zodで動的キーを持つオブジェクトのバリデーション方法
問題: Zodで動的キーを持つオブジェクトを扱えない
TypeScriptではRecord<string, string>
のように、キーが文字列で値も文字列のオブジェクト型を簡単に定義できます。しかし、Zodで同様の動的キーを持つオブジェクトスキーマを作成しようとすると、直感的な方法がありません。
以下のような試みは動作しません:
// これは正しく動作しない例
const data = z.object({
[z.string()]: z.string(),
});
このコードでは、キーとしてz.string()
を使用しようとしていますが、Zodの.object()
メソッドは固定キーを想定しているため動的キーには対応していません。この制限を克服するための適切な解決法が必要です。
解決策: z.record()
の使用
Zodには動的キーを持つオブジェクトを扱う専用のメソッドz.record()
が用意されています。この方法ではキーと値それぞれに別々のスキーマを指定できます。
基本的な使用方法
import { z } from 'zod';
// キーと値が両方とも文字列のオブジェクト
const stringRecordSchema = z.record(z.string(), z.string());
// 検証の例
stringRecordSchema.parse({ name: 'Alice', role: 'Admin' }); // 成功
stringRecordSchema.parse({ id: 123 }); // エラー: '123' is not a string
キーの型によってスキーマを変える例
// キーが数値、値が文字列のオブジェクト
const numericKeySchema = z.record(z.number(), z.string());
numericKeySchema.parse({ 1: 'One', 2: 'Two' }); // 成功
numericKeySchema.parse({ a: 'Alpha' }); // エラー: 'a' is not a number
高度な使用法: キーのパターン制限
キーに対して正規表現を使用して特定のパターンを強制することも可能です。
const prefixedKeySchema = z.record(
z.string().regex(/^user_/), // 'user_'で始まるキーのみ許可
z.string()
);
prefixedKeySchema.parse({ user_name: 'Alice' }); // 成功
prefixedKeySchema.parse({ profile: 'Admin' }); // エラー: キーが'user_'で始まらない
TypeScriptの型推論の活用
z.record()
はTypeScriptの型推論と完全に連携します。作成したスキーマから型を抽出することで、型安全なコードを書けます。
const userConfigSchema = z.record(z.string(), z.union([z.string(), z.number()]));
// スキーマから型を推論
type UserConfig = z.infer<typeof userConfigSchema>;
/*
推論される型:
{
[key: string]: string | number;
}
*/
// 型安全な使用方法
function updateConfig(config: UserConfig, key: string, value: string | number) {
// バリデーション
const validated = userConfigSchema.parse(config);
// 型安全な操作
validated[key] = value;
}
よくあるケース別解決法
厳密なキー値ペアの定義
const strictSchema = z.record(
z.string().min(3).max(20), // キーの長さ制限
z.enum(['active', 'inactive', 'pending']) // 許可される値の列挙
);
複合型の値をもつレコード
const complexRecordSchema = z.record(
z.string(),
z.object({
id: z.number(),
status: z.boolean()
})
);
z.record()
とz.object()
の比較
メソッド | 使用目的 | キーの扱い | 主な用途例 |
---|---|---|---|
z.object() | 固定キーのオブジェクト | 特定のキー名を明示的に列挙 | フォームデータなどの固定構造 |
z.record() | 動的キーのオブジェクト | キーの型をパターンで定義 | ユーザーデータ、設定項目など |
適切な選択のアドバイス
固定プロパティと動的プロパティが混在する場合、.catchall()
メソッドを使用する代替案もあります:
const mixedSchema = z.object({
id: z.number(),
name: z.string()
}).catchall(z.string()); // 追加プロパティは文字列のみ許可
ベストプラクティス
キーの制限を明確化する
- キーに意味がある場合は
z.string().min(3)
などで制限を加える - キー名のパターンが決まっているなら正規表現を使用する
- キーに意味がある場合は
パフォーマンス考慮
- 巨大なレコードを扱う場合、
z.record()
の代わりに.catchall()
で部分検証を検討 - キー数が多いときはスキーマを細分化する
- 巨大なレコードを扱う場合、
エラーハンドリング
typescripttry { stringRecordSchema.parse(input); } catch (error) { if (error instanceof z.ZodError) { // キー別のエラー処理 const keyErrors = error.errors.filter(e => e.path.length === 0); const valueErrors = error.errors.filter(e => e.path.length > 0); } }
Zodのz.record()
を活用することで、TypeScriptのRecord<K, V>
型と同等の柔軟性を実行時バリデーションにもたらせます。動的なデータ構造を安全に扱う必要がある場面で、この方法を活用してください。