第06章:TypeScriptで作る「静的な契約」②:unknownを安全にする🧼🧠
この章でできるようになること🎯✨
- 「外から来た値」を、まず unknown で受けて事故を止められる🚧🛑
- any と unknown の違いを説明できる🙂
- 型ガード(自作OK)で unknown → ちゃんとした型 に変換できる🔁✅
- 「契約(DbC)」っぽく、未確認の値を境界で検査してから中に通すができる🚪🧱
1) なんで unknown が必要なの?🌍💦
アプリはだいたい「外から」データが来ます👇
- JSON(API / ファイル / ローカルストレージ)📦
- フォーム入力📝
- URL クエリ🔗
- 他の人が書いたコードやライブラリ🧩
ここで怖いのは、見た目はそれっぽいけど中身が違うこと😵💫 DbCでいうと、これは「まだ契約が結ばれてない状態」です🤝❌
だからこの章の合言葉はこれ👇 外から来た値は、いきなり信じない。まず unknown。 🚫➡️🧼
2) any と unknown の違い🧭🤔

「どちらも何でも入る型」だけど、性質が真逆だよ👇
any:何でもできちゃう(でも事故る)💥
- コンパイラがほぼ口出ししない
- その場ではラクだけど、あとで壊れる😇
unknown:何もできない(だから安全)🛡️
- いきなりプロパティ参照できない
- まず「確認(チェック)」をしないと進めない
- つまり **「契約が確認できるまで通さない」**が自然に作れる🚪✅
3) unknown を受ける“境界”を決めよう🚧🧭

DbCの考え方では、境界(Boundary)で厳しくが基本です🙂 たとえば👇
- APIレスポンスを受け取る層
- JSON.parse した直後
- フォームの送信ハンドラ
- ルーティング(URLパラメータ)
境界で unknown にして、そこから先(中心ロジック)には ちゃんとした型だけ流す✨
4) まずは最小の型ガード:typeof からいこう🚦✨
例:unknown を string にしてから使う🙂
export function toUpperSafe(input: unknown): string {
if (typeof input !== "string") {
throw new Error("文字列を入れてね🙂(stringが必要です)");
}
// ここから下は input が string として扱える✅
return input.toUpperCase();
}
ポイント🎯
- if の中で「契約チェック」してるイメージ
- 通った後は、TypeScriptがちゃんと型を絞ってくれる✨
5) “よくある罠”も一緒に覚えよう🕳️😵💫
罠①:typeof null は "object" 😭
export function isObjectLike(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
罠②:配列は object 扱い(typeof では区別できない)📚
export function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(v => typeof v === "string");
}
罠③:"in" 演算子は相手が object っぽくないと危ない🧨
export function hasKey(value: unknown, key: string): value is Record<string, unknown> {
return typeof value === "object" && value !== null && key in value;
}
6) 自作の型ガードで「読める契約」にする🧩📝
チェックを毎回ベタ書きすると、すぐこうなります👇
- 条件が長い
- 意図が読めない
- コピペ地獄👻
だから 型ガード関数に名前をつけるのが最強です💪✨
例:ユーザー入力を “それっぽい形” にする👤✅
目標:unknown を User に変換したい!
type User = {
id: string;
name: string;
};
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) return false;
const v = value as Record<string, unknown>;
return typeof v.id === "string" && typeof v.name === "string";
}
export function parseUser(input: unknown): User {
if (!isUser(input)) {
throw new Error("ユーザー情報の形がちがうよ🥲(idとnameが必要)");
}
return input;
}
ここで起きてること🧠✨
- isUser が「契約チェック」
- parseUser は「契約を結んだ後の世界」🎀
7) JSONは特に危険!まず unknown に入れよう📦🧯
TypeScriptでは JSON.parse の結果が “強めに信用されがち” なので、意識して unknown に落とすのが安全です🙂
export function parseJsonUnknown(text: string): unknown {
return JSON.parse(text) as unknown;
}
export function loadUserFromJson(text: string): { id: string; name: string } {
const raw = parseJsonUnknown(text);
// ここで型ガード!
if (typeof raw !== "object" || raw === null) {
throw new Error("JSONがオブジェクトじゃないよ🥲");
}
const v = raw as Record<string, unknown>;
if (typeof v.id !== "string" || typeof v.name !== "string") {
throw new Error("JSONに id / name が入ってないよ🥲");
}
return { id: v.id, name: v.name };
}
8) VS Codeでのミニ実験🧪💻
手順(小さく試す)
- 適当なフォルダを作って開く📁
- ターミナルで TypeScript を用意する(プロジェクトごと)⬇️
npm init -y
npm i -D typescript
npx tsc --init
- "index.ts" を作って、次のコードを貼る⬇️
function demo(x: unknown) {
// console.log(x.toUpperCase()); // ← これは怒られる(安全!)😌
if (typeof x === "string") {
console.log(x.toUpperCase()); // ← ここはOK✅
}
}
demo("hello");
demo(123);
- タイプチェックしてみる⬇️
npx tsc --noEmit
補足(最新の動き)📌
- TypeScript 5.9 の "tsc --init" は、最初から厳しめの設定(strict など)を含む形にアップデートされています。(Microsoft for Developers)
- さらに将来(TypeScript 6.0/7.0 系)では、既定設定の見直しや高速化が進む方針が公開されています。(Microsoft for Developers)
9) AI支援(Copilot/Codex)での使い方🤖✨
AIに丸投げすると “動くけど危ない型ガード” も出やすいので、チェック観点を先に渡すのがコツです🙂
コピペ用プロンプト例🧠✍️
-
「TypeScriptで unknown を User 型に安全に変換する関数を書いて。条件:null対策、配列対策、id/name が string のときだけ通す。失敗時は分かりやすい日本語エラー。」
-
「unknown の入力を検証する型ガード関数を作って。条件:object判定、必要キーの存在確認、型チェック。テストしやすい形にして。」
AIが出したコードは、ここだけ見てチェック✅
- null を弾いてる?
- Array を弾いてる?(必要なら)
- エラーメッセージが “直し方” になってる?🧭
- value as Record<string, unknown> の乱用になってない?😵
章末まとめ📌✨
- unknown は「まだ契約が結ばれてない値」🚫🤝
- だから チェックを通るまで何もできない → 事故が減る🛡️
- 型ガードを関数化すると、契約が “読める” ようになる🧩📝
- 境界で unknown → 中心へは正しい型だけ、が気持ちいい設計🚪🧱
章末チェックリスト✅🌸
- unknown を受けた直後に、typeof / Array.isArray / null対策をしてる?
- 「ベタ書きの長い条件」を、型ガード関数にできてる?
- unknown のまま中心ロジックに流してない?(境界で止めてる?)🚧
- エラー文が「何がダメで、どう直せばいいか」になってる?🧭
演習🧪✨(手を動かすと一気に身につくよ!)
演習1:unknown を string にしてから処理する🚦
- 関数 "trimSafe(input: unknown): string" を作る
- string 以外はエラー
- string なら前後の空白を除去して返す
演習2:unknown を “商品データ” にする🛍️
型👇
- Product = { sku: string; price: number } 課題👇
- isProduct(value: unknown): value is Product を作る
- parseProduct(input: unknown): Product を作る(失敗時は分かりやすいエラー)
演習3:unknown の配列を検査する📚
- unknown が "stringの配列" のときだけ通す isStringArray を作る
- それ以外はエラーにする parseStringArray を作る
次の章では、「型だけじゃ守れない(実行時チェックが必要な)契約」の話に進むよ〜!🌍🛡️