APIレスポンスを扱うTypeScript開発で避けて通れないのが、JSONに対応する型定義です。数十行のネストしたJSONを目で追いながら interface を手書きするのは、退屈なうえにタイプミスの温床にもなります。

JSONからTypeScript型定義を自動生成する方法:実例で学ぶ変換のコツ

この記事では、実際のJSONがどんなTypeScriptの型になるのかを変換例で示しながら、自動生成のコツと注意点を解説します。

基本:フラットなオブジェクトの変換

まずは単純な例から。次のJSONを見てください。

{
  "id": 42,
  "name": "Alice",
  "isActive": true
}

これは次のような型になります。各値の型(number / string / boolean)は、実際の値から機械的に推論できます。

interface User {
  id: number;
  name: string;
  isActive: boolean;
}

ネストと配列を含む変換

実際のAPIレスポンスは、オブジェクトの入れ子や配列を含みます。

{
  "id": 42,
  "name": "Alice",
  "roles": ["admin", "editor"],
  "profile": {
    "age": 30,
    "city": "Tokyo"
  }
}

配列は要素の型から string[] のように、ネストしたオブジェクトは別のinterfaceとして切り出すのが定石です。

interface Profile {
  age: number;
  city: string;
}

interface User {
  id: number;
  name: string;
  roles: string[];
  profile: Profile;
}

つまずきやすいポイント

null と省略可能プロパティ

JSONに null が入っている場合、その値だけからは「常にnullになりうるのか」「たまたまnullだったのか」は判断できません。

{ "nickname": null, "deletedAt": null }

機械的に変換すると nickname: null になりますが、実務では string | nullnickname?: string が正しいことがほとんどです。生成後に、APIの仕様に合わせて手で調整するのが現実的な運用です。

interface User {
  nickname: string | null;   // 値が来る場合もある
  deletedAt?: string;        // フィールド自体が無い場合もある
}

数値の文字列に注意

"price": "1980" のように、数値が文字列でラップされているAPIは珍しくありません。生成ツールはこれを string と推論します。アプリ側で計算するなら、型を number に直すか、受け取り後に変換する方針を決めておきましょう。

空配列は型が決まらない

"tags": [] のような空配列からは要素の型を推論できず、never[]any[] になりがちです。要素のあるサンプルを使うか、生成後に string[] などへ手で指定します。

自動生成を使う手順

  1. ブラウザのNetworkタブやcurlの出力から、実際のレスポンスをコピーする
  2. JSON → TypeScript型生成ツール に貼り付ける
  3. ルートのinterface名を Root から UserResponse などに変更する
  4. null や省略可能フィールドを仕様に合わせて調整する
  5. プロジェクトに貼り付けて完了

ポイントは、できるだけ本番に近い、フィールドが揃ったサンプルを使うことです。サンプルが貧弱だと、省略可能フィールドや型のゆれを取りこぼします。

型だけで足りないときは Zod へ

TypeScriptの型は「コンパイル時」のチェックです。外部APIから実際に届くデータが型どおりである保証はありません。実行時にもデータの形を検証したいなら、JSON → Zodスキーマ生成 でバリデーションスキーマを作り、schema.parse(data) で入口チェックを入れるのがおすすめです。Zodなら型推論も効くため、型とバリデーションを一元管理できます。

よくある質問

生成された型のルート名を毎回変更するのが面倒です

多くのツールはルートを Root という汎用名で出力します。これはコピー後にプロジェクトの命名規則(ApiResponseUserProfile など)へ一括置換するのが楽です。ネストしたinterface名は内容に即した名前になっていることが多いので、ルートだけ直せば十分なケースがほとんどです。

snake_case のAPIをcamelCaseで使いたい

生成される型はJSONのキーをそのまま反映するため、created_at のような名前になります。アプリ全体をcamelCaseで統一したい場合は、API境界で変換する(マッピング関数やcamelize系ライブラリを使う)のが定石です。型定義はAPIの実体に合わせ、変換は明示的に行うと混乱しません。

TypeScriptの型とZodスキーマ、どちらを先に作るべき?

実行時バリデーションが必要なら、Zodスキーマを作って z.infer で型を導出する方法が一元管理できておすすめです。型チェックだけで十分な内部データなら、TypeScript型の生成だけで構いません。外部APIやフォーム入力など「信頼できない入力」にはZodを使うと安全です。