JSONからReadonlyなTypeScript Interfaceを生成する

JSONデータからreadonlyプロパティを持つTypeScript interfaceを生成する方法を学びます。型レベルでの不変性を強制し、意図しない変更を防止。

Advanced Patterns

詳細な説明

JSONからの不変型

デフォルトでは、TypeScript interfaceのすべてのプロパティはミュータブル(変更可能)です。readonlyを追加すると、構築後の再代入を防ぎ、コンパイル時に意図しないミューテーションを検出できます。

JSON例

{
  "id": "usr_001",
  "createdAt": "2024-01-15T08:00:00Z",
  "name": "Alice",
  "permissions": ["read", "write"]
}

生成されるTypeScript(readonly)

interface User {
  readonly id: string;
  readonly createdAt: string;
  readonly name: string;
  readonly permissions: readonly string[];
}

Readonlyを使うべき場合

  • IDとタイムスタンプ — サーバーによって設定され、クライアントコードで変更すべきではないもの。
  • 設定オブジェクト — 一度パースされ、どこでも使用され、決してミューテートされないもの。
  • Redux/状態管理 — 不変状態パターンはreadonly型の恩恵を受けます。

Readonly vs Readonly<T>

プロパティごとにreadonlyを適用するか、組み込みユーティリティ型を使用できます:

// プロパティごと
interface User {
  readonly id: string;
  name: string; // ミュータブル
}

// 全プロパティ
type ImmutableUser = Readonly<User>;

Readonly<T>は浅い(shallow)ため、ネストされたオブジェクトは再帰的に適用しない限りミュータブルのままです:

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

Readonly配列

readonly string[]またはReadonlyArray<string>を使用して、.push().pop()、直接インデックス代入を防止します。状態管理における配列では、ミューテーションがバグの原因となるため、これは特に重要です。

実践的なガイドライン

デフォルトですべてをreadonlyにし、明示的にミューテーションが必要な場合にのみ除去してください。これは最小権限の原則に沿っており、多くのバグを検出できます。

ユースケース

Reduxストアを構築し、すべての状態スライスをデフォルトで不変にして、意図しない直接ミューテーションをランタイムのバグではなく開発中に検出したい場合に使用します。

試してみる — JSON to TypeScript

フルツールを開く