- TypeScript
- 技術 💻
TypeScriptのanyとunknownの違い|絞り込み方と使い分けの判断表
TypeScriptのanyとunknownの違いは、anyは「型チェックをしない型」、unknownは「中身を確かめるまで使えない型」であることです。 どちらも何でも代入できますが、unknownは typeof などで型を絞り込むまでプロパティにアクセスできません。そのため、型がわからない値を受け取るときは、まずunknownを選ぶのが安全です。
この記事では、TypeScript 7.0.2(2026年9月時点のnpm最新版)で tsc を実行した実際のエラー文と、Node.js 22での実行結果を載せながら、unknownの絞り込み方(typeof・instanceof・in・型ガード関数・asserts)、catch句の e がunknownになる設定、JSON.parse の結果をzodで検証する方法、neverとの関係、既存コードのanyをunknownへ移す手順までを解説します。最後に「どちらを使うか」の判断表も用意しました。
anyとunknownの違いの早見表
| 比べる点 | any | unknown |
|---|---|---|
| 何でも代入できるか | できる | できる |
| 他の型の変数に代入できるか | できる(チェックなし) | できない(any と unknown にだけ代入可) |
| プロパティ・メソッドを使えるか | 使える(存在しなくても通る) | 絞り込むまで使えない |
| ミスに気づくタイミング | 実行時 | コンパイル時 |
| 型の位置づけ | 型チェックの対象外 | すべての型の上位(トップ型) |
| 主な使いどころ | 移行途中の一時的な逃げ道 | 外部入力、catch の e、JSON.parse の結果 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptのanyとunknownの違い|絞り込み方と使い分けの判断表」
検証環境
- TypeScript 7.0.2(
tsconfig.jsonで"strict": true。7.0ではstrictは既定でもtrue) - Node.js 22.22.2(型注釈を取り除いてそのまま
.tsを実行) - zod 4.6.5
- ESLint 10.11.0 / typescript-eslint 8.70.1(このときpeer依存の関係でTypeScript 6.0.3が入りました)
同じコードでanyとunknownを比べる
let a: any = "hello";
let u: unknown = "hello";
a.toFixed(2); // コンパイルは通る
u.toFixed(2);
const s1: string = a; // any はどこにでも代入できる
const s2: string = u;
const x: unknown = a; // unknown には何でも代入できる
tsc の結果です。
src/c1.ts(4,1): error TS18046: 'u' is of type 'unknown'.
src/c1.ts(6,7): error TS2322: Type 'unknown' is not assignable to type 'string'.
unknownの行だけがエラーになり、anyの行は通りました。 ところが、anyの行をJavaScriptとして実行すると次のようになります。
TypeError: a.toFixed is not a function
anyは「間違っていても通す」、unknownは「確かめるまで通さない」と覚えると違いがはっきりします。
unknownの絞り込み方(型の絞り込み=ナローイング)
unknownの値は、条件分岐で型を確かめると、その分岐の中だけ具体的な型として扱えます。 これを「型の絞り込み(ナローイング)」と呼びます。
| 方法 | 確かめられるもの | 例 |
|---|---|---|
typeof |
プリミティブ型(string, number, boolean, bigint, symbol, undefined, function, object) | typeof v === "string" |
instanceof |
クラスのインスタンス(Error, Date, 自作クラス) | v instanceof Date |
Array.isArray |
配列 | Array.isArray(v) |
in |
オブジェクトが特定のキーを持つか | "id" in v |
| 型ガード関数 | 自分で決めた条件 | function isUser(v): v is User |
asserts |
条件を満たさなければ例外 | function assertUser(v): asserts v is User |
| スキーマ検証 | 複雑な構造をまとめて | zodの safeParse |
typeof・instanceof・in の組み合わせ
class HttpError extends Error {
status: number;
constructor(status: number, message: string) { super(message); this.status = status; }
}
function describe(v: unknown): string {
if (typeof v === "string") return `string(${v.length})`;
if (typeof v === "number") return `number(${v.toFixed(1)})`;
if (v instanceof HttpError) return `HttpError(${v.status})`;
if (v instanceof Date) return `Date(${v.toISOString()})`;
if (Array.isArray(v)) return `array(${v.length})`;
if (typeof v === "object" && v !== null && "id" in v) return `has id: ${String(v.id)}`;
return "unknown";
}
for (const v of ["abc", 3.14159, new HttpError(404, "nf"), new Date(0), [1, 2], { id: 7 }, null]) {
console.log(describe(v));
}
実行結果です。
string(3)
number(3.1)
HttpError(404)
Date(1970-01-01T00:00:00.000Z)
array(2)
has id: 7
unknown
各分岐の中では v.length や v.status がエラーなく使えています。 TypeScriptが条件から型を判断しているためです。
よくあるつまずき:typeof v === "object" だけでは足りない
function getId(v: unknown) {
if (typeof v === "object") {
return v.id;
}
}
src/c7_bad.ts(3,12): error TS18047: 'v' is possibly 'null'.
src/c7_bad.ts(3,14): error TS2339: Property 'id' does not exist on type 'object'.
JavaScriptでは typeof null も "object" になるため、v !== null の確認が必要です。 さらに "id" in v でキーの存在を確かめると、v.id にアクセスできるようになります(前の例の最後の分岐)。
型ガード関数とasserts関数
同じ確認を何度も書くなら、戻り値の型を v is User にした「型ガード関数」にまとめます。例外で止めたい場合は asserts v is User を使います。
type User = { id: number; name: string };
function isUser(v: unknown): v is User {
return (
typeof v === "object" && v !== null &&
"id" in v && typeof v.id === "number" &&
"name" in v && typeof v.name === "string"
);
}
function assertUser(v: unknown): asserts v is User {
if (!isUser(v)) throw new TypeError("not a User");
}
const data: unknown = JSON.parse('{"id":1,"name":"Sato"}');
if (isUser(data)) console.log("guard:", data.name.toUpperCase());
const data2: unknown = JSON.parse('{"id":"1"}');
try {
assertUser(data2);
console.log(data2.name); // この行以降、data2 は User として扱われる
} catch (e) {
console.log("assert:", e instanceof Error ? e.message : e);
}
guard: SATO
assert: not a User
| 書き方 | 条件を満たさないとき | 向いている場面 |
|---|---|---|
v is User(型ガード) |
false を返す |
if文で分岐したい |
asserts v is User |
例外を投げる | 以降の処理を前提条件つきで書きたい |
型ガード関数の中身が正しいかどうかはTypeScriptは確認しません。 条件を書き漏らすと、誤った型が付いてしまう点に注意します。構造が大きい場合は、次に紹介するzodのような検証ライブラリに任せると確実です。
catch句のeはunknown(useUnknownInCatchVariables)
strict: true のとき、catch (e) の e はunknownになります。 これは useUnknownInCatchVariables という設定で、strict に含まれています。JavaScriptでは throw "文字列" のようにError以外の値も投げられるためです。
try {
JSON.parse("{broken");
} catch (e) {
console.log(e.message);
}
src/c4_catch.ts(4,15): error TS18046: 'e' is of type 'unknown'.
--useUnknownInCatchVariables false を付けて同じファイルをチェックすると、このエラーは出ませんでした(e がanyになるため)。unknownのまま、Errorかどうかを確かめて使うのがおすすめです。
function toMessage(e: unknown): string {
if (e instanceof Error) return e.message;
if (typeof e === "string") return e;
return JSON.stringify(e);
}
try {
JSON.parse("{broken");
} catch (e) {
console.log(toMessage(e));
}
try { throw "plain string"; } catch (e) { console.log(toMessage(e)); }
try { throw { code: 42 }; } catch (e) { console.log(toMessage(e)); }
Expected property name or '}' in JSON at position 1 (line 1 column 2)
plain string
{"code":42}
Errorでない値が投げられても、落ちずにメッセージを取り出せています。
JSON.parseの戻り値はanyなので、unknownで受けてzodで検証する
JSON.parse の戻り値の型は any です。 そのため、次のコードはコンパイルを通りますが、実行すると意図しない結果になります。
const cfg = JSON.parse('{"port":"8080"}');
const port: number = cfg.port; // エラーにならない
console.log(port + 1);
80801
port は number と宣言されていますが、中身は文字列の "8080" だったため、足し算が文字列の連結になりました。 外部から来るデータ(APIレスポンス、設定ファイル、フォーム入力、localStorage)は、unknownで受け取って検証してから型をつけると安全です。
zodでの検証(zod 4.6.5で実行)
import * as z from "zod";
const User = z.object({
id: z.number().int(),
name: z.string().min(1),
email: z.email().optional(),
});
type User = z.infer<typeof User>; // スキーマから型を自動生成
function parseUser(json: string): User | null {
const raw: unknown = JSON.parse(json);
const result = User.safeParse(raw);
if (!result.success) {
console.log(z.prettifyError(result.error));
return null;
}
return result.data; // ここでは User 型
}
console.log(parseUser('{"id":1,"name":"Sato"}'));
console.log(parseUser('{"id":"1","name":"","email":"x"}'));
{ id: 1, name: 'Sato' }
✖ Invalid input: expected number, received string
→ at id
✖ Too small: expected string to have >=1 characters
→ at name
✖ Invalid email address
→ at email
null
スキーマを1つ書くだけで、実行時の検証とTypeScriptの型が両方手に入ります。 型ガード関数を手で書くより漏れが起きにくく、エラー内容もどの項目が違うかまでわかります。なお、zod 4ではメールアドレスの検証は z.email() のようにトップレベルの関数で書けます。
unknown・any・neverの関係
unknownは「すべての型を含む一番上の型(トップ型)」、neverは「値が1つもない一番下の型(ボトム型)」です。 anyはこの階層の外にいて、型チェックそのものを行いません。
flowchart TD
U["unknown(トップ型)<br>何でも入るが、そのままでは使えない"]
U --> S[string]
U --> N[number]
U --> O["object など"]
S --> NV["never(ボトム型)<br>値が存在しない"]
N --> NV
O --> NV
A["any(階層の外)<br>型チェックをしない"]
| 式 | 結果 | 理由 |
|---|---|---|
unknown | string |
unknown |
一番大きい型に吸収される |
never | string |
string |
空の型は消える |
unknown & string |
string |
共通部分は小さい方 |
neverで分岐の漏れを見つける
type Shape = { kind: "circle"; r: number } | { kind: "square"; size: number } | { kind: "tri"; base: number; h: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.r ** 2;
case "square": return s.size ** 2;
default: {
const unreachable: never = s;
throw new Error(`unexpected: ${JSON.stringify(unreachable)}`);
}
}
}
src/c6_never.ts(7,13): error TS2322: Type '{ kind: "tri"; base: number; h: number; }' is not assignable to type 'never'.
"tri" の分岐を書き忘れたことを、コンパイル時に教えてくれます。 unknownが「何でも入る箱」なら、neverは「何も入らない箱」で、この性質を使うと網羅チェックができます。
anyからunknownへの移行手順
既存コードのanyは、一度に全部ではなく、影響の小さい場所から順にunknownへ置き換えると進めやすくなります。
| 手順 | やること | ポイント |
|---|---|---|
| 1 | strict: true を有効にする |
catch の e がunknownになり、noImplicitAny も有効になる |
| 2 | ESLintで no-explicit-any を有効にし、anyの場所を一覧にする |
まずは warn から始めてもよい |
| 3 | 外部入力の境界(API・JSON・catch)をunknownにする | ここが最も効果が大きい |
| 4 | unknownにした箇所を型ガードやzodで絞り込む | エラーが出た場所が確認ポイント |
| 5 | 内部の関数は具体的な型やジェネリクスに置き換える | any[] → T[] など |
| 6 | 残すanyには理由をコメントで書く | // eslint-disable-next-line と理由をセットで |
ESLintのno-explicit-anyを設定する
typescript-eslintの recommended 設定には、@typescript-eslint/no-explicit-any がエラーとして含まれています。 fixToUnknown: true を付けると、--fix でanyをunknownに書き換えられます。
// eslint.config.js
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
export default defineConfig(
tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }],
},
},
);
// src/sample.ts(修正前)
export function load(raw: any): any {
return raw;
}
npx eslint src の結果です。
src/sample.ts
1:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1:33 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
✖ 2 problems (2 errors, 0 warnings)
2 errors and 0 warnings potentially fixable with the `--fix` option.
npx eslint src --fix の後のファイルです。
export function load(raw: unknown): unknown {
return raw;
}
自動修正でunknownに変えたあと、tsc で出たエラーの場所が、絞り込みを追加すべき場所です。 自動修正は機械的な置き換えなので、変更後は必ず tsc とテストで確認します。
anyとunknownの使い分け判断表
| 場面 | おすすめ | 理由 |
|---|---|---|
APIレスポンス・JSON.parse の結果 |
unknown+検証 | 実データの形は実行時まで不明 |
catch (e) の e |
unknown | Error以外も投げられる |
| 何でも受け取ってログに出すだけ | unknown | 中身を使わないなら絞り込みも不要 |
| 配列や値の型を保ったまま処理 | ジェネリクス | T で型を引き継げる |
| 型定義のない外部ライブラリ | unknownで受けて型を宣言 | 使う部分だけ型を書く |
| JavaScriptからの移行途中 | 一時的にany | 理由をコメントし、段階的に置き換え |
| テストのモック | 状況に応じてany | 範囲をテストファイル内に限定 |
迷ったら「unknownにして、使う直前に絞り込む」を基本にすると、型安全と書きやすさのバランスが取れます。
よくある質問
Q. unknownは毎回絞り込むのが面倒ではないですか? A. 絞り込みはデータの入口で1回行えば十分です。境界でzodや型ガード関数を通して具体的な型にすれば、それより内側のコードは普通の型として扱えます。
Q. as で型を付ければunknownでも使えますか?
A. value as User と書けばコンパイルは通りますが、実行時の確認は行われません。 中身が違っていても気づけないため、外部データには型ガードや検証を使うのがおすすめです。
Q. object 型や {} 型とunknownは何が違いますか?
A. object はプリミティブ以外、{} は null と undefined 以外の値を表します。null や undefined も含めて本当に何が来るかわからないときはunknownを使います。
Q. anyを使ってよい場面はありますか? A. JavaScriptから移行している途中や、型の表現がとても難しい一部の箇所など、一時的な逃げ道として使われます。範囲を小さくし、理由をコメントに残しておくと後から置き換えやすくなります。
まとめ
- anyは型チェックをしない型、unknownは確かめるまで使えない型
- unknownは typeof・instanceof・in・型ガード関数・asserts で絞り込む
typeof v === "object"の後はv !== nullの確認も忘れずにstrict: trueならcatch (e)のeはunknown。Errorかどうか確かめて使うJSON.parseの結果はunknownで受けてzodで検証すると、型と実データがずれない- unknownはトップ型、neverはボトム型。neverで分岐の漏れを検出できる
- 移行は
no-explicit-any+fixToUnknownでanyを洗い出し、入口から順に置き換える
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptのanyとunknownの違い|絞り込み方と使い分けの判断表」
株式会社RJCでは、型を味方につけた安全なコードの書き方を、メンバー同士で学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。