Skip to content

Zodで動的キーを持つオブジェクトのバリデーション方法

問題: Zodで動的キーを持つオブジェクトを扱えない

TypeScriptではRecord<string, string>のように、キーが文字列で値も文字列のオブジェクト型を簡単に定義できます。しかし、Zodで同様の動的キーを持つオブジェクトスキーマを作成しようとすると、直感的な方法がありません。

以下のような試みは動作しません

typescript
// これは正しく動作しない例
const data = z.object({
  [z.string()]: z.string(),
});

このコードでは、キーとしてz.string()を使用しようとしていますが、Zodの.object()メソッドは固定キーを想定しているため動的キーには対応していません。この制限を克服するための適切な解決法が必要です。


解決策: z.record()の使用

Zodには動的キーを持つオブジェクトを扱う専用のメソッドz.record()が用意されています。この方法ではキーと値それぞれに別々のスキーマを指定できます。

基本的な使用方法

typescript
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

キーの型によってスキーマを変える例

typescript
// キーが数値、値が文字列のオブジェクト
const numericKeySchema = z.record(z.number(), z.string());

numericKeySchema.parse({ 1: 'One', 2: 'Two' }); // 成功
numericKeySchema.parse({ a: 'Alpha' }); // エラー: 'a' is not a number

高度な使用法: キーのパターン制限

キーに対して正規表現を使用して特定のパターンを強制することも可能です。

typescript
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の型推論と完全に連携します。作成したスキーマから型を抽出することで、型安全なコードを書けます。

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;
}

よくあるケース別解決法

厳密なキー値ペアの定義

typescript
const strictSchema = z.record(
  z.string().min(3).max(20), // キーの長さ制限
  z.enum(['active', 'inactive', 'pending']) // 許可される値の列挙
);

複合型の値をもつレコード

typescript
const complexRecordSchema = z.record(
  z.string(),
  z.object({
    id: z.number(),
    status: z.boolean()
  })
);

z.record()z.object()の比較

メソッド使用目的キーの扱い主な用途例
z.object()固定キーのオブジェクト特定のキー名を明示的に列挙フォームデータなどの固定構造
z.record()動的キーのオブジェクトキーの型をパターンで定義ユーザーデータ、設定項目など

適切な選択のアドバイス

固定プロパティと動的プロパティが混在する場合、.catchall()メソッドを使用する代替案もあります:

typescript
const mixedSchema = z.object({
  id: z.number(),
  name: z.string()
}).catchall(z.string()); // 追加プロパティは文字列のみ許可

ベストプラクティス

  1. キーの制限を明確化する

    • キーに意味がある場合はz.string().min(3)などで制限を加える
    • キー名のパターンが決まっているなら正規表現を使用する
  2. パフォーマンス考慮

    • 巨大なレコードを扱う場合、z.record()の代わりに.catchall()で部分検証を検討
    • キー数が多いときはスキーマを細分化する
  3. エラーハンドリング

    typescript
    try {
      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>型と同等の柔軟性を実行時バリデーションにもたらせます。動的なデータ構造を安全に扱う必要がある場面で、この方法を活用してください。