APIレスポンスの型定義がずれる5つの原因と確かめ方
型を書いたのに実行時に undefined が来る、IDが別のレコードを指す。APIレスポンスと型定義が食い違う代表的な5つの原因を、見分け方と直し方に分けて整理しました。実データから型を起こし直して差分を取る手順も紹介します。
公開読了目安 約5分
この記事の要点
- 型定義は実行時に何も検査しないので、ずれても誰も止めてくれない
- 1件のレスポンスから起こした型は、省略される項目を必須にしてしまう
- null と「キーが無い」は別物で、混ぜると分岐が抜ける
- 安全に扱える整数を超えるIDを number で受けると値そのものが変わる
- 実データを複数件集めて型を起こし直し、差分を取ると原因が絞れる
目次
型を書いたのに実行時に undefined が来る。その原因のほとんどは、型定義が間違っているのではなく、型を作ったときに見ていたデータが足りなかったことにあります。この記事では、型と実データが食い違う代表的な 5 つの原因を、見分け方と直し方に分けて整理します。
前提: 型定義は実行時に何も守らない
まずここを押さえてください。TypeScript の型は、コードを書いている間だけの約束です。
ビルドすると型の情報は消えます。実行時にサーバーから何が届いても、型が違うと言って止まることはありません。
つまり型と実データのずれは、誰も検出してくれないまま先へ進みます。気づくのは、そのデータを使った画面が壊れたときです。
原因1: 1 件のレスポンスから型を起こした
一番多いのがこれです。手元にあるサンプルのレスポンスを 1 件だけ見て型を書くと、そのとき返ってきた項目が全部必須になります。
省略されることがある項目は、無かった回のレスポンスを見ない限り分かりません。1 件では判断する材料がそもそも無いのです。
見分け方: 落ちた項目が、実データの一部にだけ存在するかを確かめます。3 件ほど並べて見比べれば分かります。
直し方: 抜けることがある項目に ? を付けます。そのうえで、使う側で有無を分岐させてください。
原因2: null と「キーが無い」を混ぜている
この 2 つは似て見えますが、届き方が違います。
| 届いたもの | 型での書き方 | 読み出したときの値 |
|---|---|---|
"bio": null | bio: string | null | null |
bio というキーが無い | bio?: string | undefined |
| どちらも起こりうる | bio?: string | null | null か undefined |
if (user.bio) のように真偽で分岐すると、どちらも同じ扱いになるので気づきません。問題が出るのは user.bio.length のように中身へ触ったときです。
見分け方: 実データで、そのキーが存在しない回とnull が入っている回の両方があるかを数えます。
直し方: 両方あるなら ? と | null を両方書きます。片方しか無いなら片方だけにして、意図を型に残してください。
原因3: 大きな ID を number で受けている
外部サービスの ID には、19 桁前後の整数がよく使われます。JavaScript が正確に表せる整数は 9007199254740991 まで、つまり 16 桁です。
これを超える値は、JSON として読み込んだ時点で丸められます。型としては number で通り、エディタも実行時も何も言いません。
気づくのは、保存した ID が別のレコードを指していたときです。表示は正常に見えるぶん、発見がかなり遅れます。
見分け方: レスポンスの生テキストに書かれた数値と、読み込んだ後の値を見比べます。末尾が 000 になっていたら丸められています。
直し方: サーバー側が文字列で返せるならそれが最善です。返せないなら、テキストのまま取り出して文字列として扱ってください。
原因4: 日付を Date 型として書いている
JSON に日付という型はありません。"2026-08-27T10:00:00Z" は、あくまで文字列として届きます。
型定義に createdAt: Date と書くと、その場では通ります。しかし実際に入っているのは文字列なので、getFullYear() を呼んだ瞬間に落ちます。
見分け方: そのプロパティに対してメソッドを呼んでいる箇所を探します。日付の計算をしている場所が候補です。
直し方: 型は string のままにします。日付として扱いたい場所で、受け取ったあとに変換してください。
原因5: 型を書いたあとに API が変わった
型定義は、一度書くとそのまま残ります。一方で API は、項目が増えたり、値の形が変わったりします。
追加された項目は、型に無くても実害が出ません。困るのは消えた項目と形が変わった項目で、どちらも実行時まで表面化しません。
見分け方: 型定義の更新日と、API の変更履歴を突き合わせます。履歴が無い場合は、実データから型を起こし直して差分を見ます。
直し方: 型を実データ起点で作り直す習慣にします。手で追記していくと、消えた項目がいつまでも残ります。
原因を絞る 3 つの手順
どれが原因か分からないときは、この順で進めると早く絞れます。
手順1: 実データを 3 件以上集める。開発環境のログでも、ブラウザの通信タブに出たレスポンスでもかまいません。1 件では原因1 と原因2 が見分けられません。
手順2: 実データから型を起こし直す。手で書くと、いま持っている思い込みがそのまま入ります。機械的に起こすほうが、思い込みの外にある差が出ます。
JSON→TypeScript型生成ツールに集めたレスポンスを続けて貼ってください。全件をまとめた型と、各項目が何件にあったかの表が出ます。桁が落ちた数値や、そのままでは書けないキーもその場で知らされます。
手順3: いま使っている型と見比べる。差が出た項目が原因の候補です。? の有無、| null の有無、number か string かの 3 点を先に見てください。
型のずれを繰り返さないための線引き
原因を直したあと、同じことが起きないようにする方法は 2 つあります。
入口で 1 度だけ検査する。データが入ってくる場所、つまり通信の直後に検査を挟みます。ここを通ったあとは型を信じてよい、という線を引く考え方です。
型を実データから起こす習慣にする。手で書き足すのをやめ、レスポンスを貼って起こし直す形にします。項目が消えたことにも気づけます。
どちらも「型は実行時に何も守らない」という前提から出てきた対策です。守ってくれないなら、守る場所を自分で 1 箇所だけ作る、という発想になります。
まとめ
型定義と実データのずれは、5 つの原因にだいたい収まります。省略される項目、null と欠落の混同、大きな整数の桁落ち、日付の扱い、API の変更です。
どれも実行時まで表面化しないので、実データを集めて型を起こし直すのが一番早い切り分けになります。3 件並べれば、原因1 と原因2 はその場で見分けられます。
そのうえで、通信の直後に検査を 1 箇所だけ置いてください。型が守ってくれない範囲を、自分で線引きしておくのが確実です。
あわせて読みたい
同じテーマ・関連ツールの記事です。