JavaScript / TypeScript公開 約8分

TypeScript unknownで受信データを検査する:型と実行時の確認を分ける

型を付けてもAPIから届く値が正しいとは限りません。unknownを入口に置き、名前と在庫数を確認して必要な値を作り直します。型検査と実行時の結果を別々に照合する練習です。

この記事の目次
  1. 型を書く場所と、値を検査する場所
  2. 受け付けるデータを先に決める
  3. unknownから必要な二項目を作り直す
  4. 分岐の順番を省くと、確認前に止まる
  5. 検査の境界を増やしすぎない
  6. 不正な値を追加し、二種類の検査を照合する

APIから受け取る値に型を付けたのに、画面を開くと名前が読めずに止まる。TypeScript unknownを入口に置くと、「まだ形を確認していない値」と「アプリで使ってよい値」をコード上で分けられます。ただし、unknownという名前を付けるだけで検証が済むわけではありません。

JavaScriptのオブジェクトとif文が読める人向けに、架空の貸出道具のデータを確認します。HTTP通信は作らず、受信済みのJSONを模した値から、名前と在庫数を取り出すところに絞ります。通信失敗の扱いはfetchの応答確認で先に分けておきます。

型を書く場所と、値を検査する場所

型注釈は、プログラムを書く段階で値の扱いを点検するためのものです。外部サービスが実際に約束どおりの値を返すかまでは保証しません。たとえば在庫数が文字列の"3"になったデータも、JSONとしては正しい状態です。

as Toolのような型アサーションは、型について開発者の判断を伝える書き方です。TypeScript公式の型アサーションの説明では、変換後のJavaScriptから取り除かれ、実行時の検査は追加されません。エラー表示を消すためだけに使うと、入口の問題を後ろへ送ってしまいます。

一方、unknownはどんな値も受け取れますが、確認せずに名前などを読み出せません。この制約を利用し、入口の関数の中で検査を済ませます。確認後は元のオブジェクトを丸ごと渡さず、必要な二項目から新しいオブジェクトを作る方針です。

受け付けるデータを先に決める

練習では次の契約にします。在庫0は正常なデータで、名前が空白だけの場合は不正です。存在しない道具を在庫0へ置き換えることもありません。

項目 受け付ける条件 不正な例
name 前後の空白を除いて一文字以上の文字列 空白だけ、数値、項目なし
stock 0以上の安全な整数 負数、小数、文字列、Infinity
データ全体 配列やnullではないオブジェクト null、配列、数値

この契約は練習用です。実際のAPIで在庫が未確定になるなら、「未確定」という別の状態を設計します。名前の最大長や商品番号の検査も、この例では省いています。二項目が通ったことを、注文や認証まで安全であるという意味に広げないでください。

unknownから必要な二項目を作り直す

空の練習フォルダでnpm init -yを実行し、npm install --save-dev --save-exact typescript@5.9.3で検証用のバージョンをそろえます。Node.jsとnpmを用意してください。例はNode.js 24系、TypeScript 5.9.3で確認する構成です。

次をtool.tsに保存します。関数の戻り値は、正しいToolか、受け付けられないことを表すnullのどちらかです。

type Tool = { name: string; stock: number };

function parseTool(value: unknown): Tool | null {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    return null;
  }
  if (!("name" in value) || !("stock" in value)) return null;
  const { name, stock } = value;
  if (typeof name !== "string" || name.trim() === "") return null;
  if (typeof stock !== "number" || !Number.isSafeInteger(stock) || stock < 0) {
    return null;
  }
  return { name: name.trim(), stock };
}

const inputs: unknown[] = [
  { name: " 三脚 ", stock: 3 },
  { name: "ライト", stock: 0 },
  { name: "ライト", stock: "3" },
  null,
  { stock: 3 },
  { name: " ", stock: -1 }
];
for (const input of inputs) {
  const tool = parseTool(input);
  console.log(tool === null ? "形式を確認してください" : `${tool.name}:${tool.stock}点`);
}

次の順序で、型検査、JavaScriptへの変換、実行を行います。strictで厳密な型検査を有効にし、型エラーがある状態では出力しない指定も付けています。

npx tsc tool.ts --strict --target ES2022 --module commonjs --noEmitOnError --outDir out
node out/tool.js

結果は「三脚:3点」「ライト:0点」、続いて同じ確認案内が四行です。型検査を通ることと、不正な入力を実行時に断れることは別の確認なので、コマンドも二段階に分けます。型検査だけ成功しても、期待する六行が出るとは限りません。

分岐の順番を省くと、確認前に止まる

最初の失敗は、nullを除く前に"name" in valueを調べることです。typeof nullはobjectなので、typeofだけでは足りません。修正ではオブジェクトであること、nullではないことを先に確認します。再実行して、nullが例外ではなく確認案内になるか照合します。

公式の型の絞り込みの説明にあるように、条件分岐の後では使える型が狭まります。この例では全体の種類、項目の存在、項目の型という順に進みます。読む前に確かめる点はfindの結果の確認とも共通しますが、ここでは外部データ全体の契約を組み立てています。

二つ目の失敗は、if (!stock)で在庫を判定することです。0も偽として扱われ、正常な在庫切れまで不正になります。修正では数値・安全な整数・0以上を別々に確認します。stockが0なら「0点」、-1や0.5なら確認案内になることを再確認してください。

三つ目は、unknownのエラーを消すためanyへ変更することです。検査なしで項目を読めるようになっても、値が正しくなったわけではありません。完成例のunknownへ戻し、別ファイルでfunction read(v: unknown) { return v.name; }を型検査すると、確認前のアクセスが拒否されることを確かめられます。この意図したエラー例は完成ファイルには追加しません。

検査の境界を増やしすぎない

この関数は、JSON由来の普通のデータをアプリ内部へ受け渡す入口です。任意のJavaScriptオブジェクトに仕込まれたgetterなどまで安全に実行できる検査ではありません。また、JSONそのものが壊れている場合は、解析の段階のエラーとして別に扱います。

同じデータを表示するたびに検査を繰り返すより、読み込んだ境界で一度確認し、内部にはToolとして渡す構成が考えられます。APIの契約が大きくなり、項目ごとのエラー説明も必要なら、スキーマ検証の仕組みを検討します。ただし型定義と検査規則が別々に変わらない運用も必要です。

不正な値を追加し、二種類の検査を照合する

15分程度を目安に、inputsへ三件を加えます。{ name: "台車", stock: 2 }は「台車:2点」、stockを0.5へ変えたものは確認案内、nameを数値へ変えたものも確認案内になるのが解答です。

さらに元のinputsの名前に付いた空白は、出力では消えていても元データには残ることを確認します。完成例は元の値を書き換えません。型エラーなし、六件と追加三件の期待結果、元データの保持を別々に記録してください。JavaScriptの無料レッスンの関数へ取り込むときも、受け付ける契約と使う処理を分けて説明できることが目標です。

TRY A LESSON

記事の内容を、課題で確かめる

前提になるJavaScriptの練習 →

React・TypeScriptの学習内容 →

記事についてのお問い合わせ

記載内容へのご質問や訂正のご連絡は、hello@junkbranding.comまでお願いします。

← 記事一覧に戻る