JSONからTypeScript判別ユニオン型を変換する

共通のタグフィールドを共有するJSONオブジェクトからTypeScript判別ユニオンを作成する方法を学びます。網羅的パターンマッチングを実現。

Complex Types

詳細な説明

JSONからの判別ユニオン

判別ユニオン(タグ付きユニオンとも呼ばれる)は、複数のinterfaceが共通のリテラル型プロパティを共有し、TypeScriptが条件分岐で型を絞り込むために使用するパターンです。

JSON例

[
  { "type": "text", "content": "Hello world" },
  { "type": "image", "url": "https://cdn.example.com/photo.jpg", "alt": "A photo" },
  { "type": "video", "url": "https://cdn.example.com/clip.mp4", "duration": 120 }
]

生成されるTypeScript

interface TextBlock {
  type: "text";
  content: string;
}

interface ImageBlock {
  type: "image";
  url: string;
  alt: string;
}

interface VideoBlock {
  type: "video";
  url: string;
  duration: number;
}

type Block = TextBlock | ImageBlock | VideoBlock;

コンバーターの判別子検出方法

ツールは以下の条件を満たすstringプロパティを探します:

  1. すべての配列要素に存在する。
  2. 少数の一意なリテラル値を持つ。
  3. 異なる形状と相関する — "type": "text"の要素は"type": "image"の要素とは異なるキーを持つ。

これらの条件が満たされると、コンバーターは個別のinterfaceとユニオン型を生成します。

パターンマッチング

function renderBlock(block: Block) {
  switch (block.type) {
    case "text":
      return block.content; // TSはblockがTextBlockであることを認識
    case "image":
      return block.url;     // TSはblockがImageBlockであることを認識
    case "video":
      return block.duration; // TSはblockがVideoBlockであることを認識
  }
}

TypeScriptは各caseブランチ内で型を絞り込み、型アサーションなしで特定のプロパティにアクセスできます。

網羅性チェック

neverを使ったdefaultブランチを追加して、ケースの漏れを検出します:

default:
  const _exhaustive: never = block;
  throw new Error(`Unhandled block type: ${(block as any).type}`);

後で"code"ブロック型を追加して処理を忘れた場合、TypeScriptがコンパイル時にエラーを報告します。

ユースケース

リッチコンテンツエディタを構築し、APIがヘテロジニアスなブロック(テキスト、画像、動画、コード)の配列を返す際に、各バリアントに対する型安全なレンダリングロジックが必要な場合に使用します。

試してみる — JSON to TypeScript

フルツールを開く