TypeScript には「オブジェクトっぽい何か」を表す型が複数あり、その中でも object(小文字)・Object(大文字)・{}(空オブジェクト型)の3つは見た目が似ているうえに挙動が大きく違うため、非常に混同されやすい存在です。「任意のオブジェクトを受け取りたい」と思って何気なくどれかを選ぶと、プリミティブまで通ってしまったり、逆にプロパティにアクセスできなかったりと、想定外の結果になります。この記事では、3つの型がそれぞれ「何を代入できて」「プロパティにアクセスできるのか」を、実際にコンパイルが通る・エラーになる挙動とあわせて整理し、最終的に any・unknown・Record<string, unknown> との違いを踏まえて「結局どれを使えばよいのか」を解説します。
目次
なぜ object・Object・{} は紛らわしいのか
この3つがややこしいのは、名前や見た目から受ける印象と、実際に許可される値がずれているからです。小文字の object は「オブジェクトの型」ですが、代入した値のプロパティには一切アクセスできません。大文字の Object は「オブジェクト全般の型」に見えて、実際には 42 や 'hello' のようなプリミティブまで受け付けてしまいます。{} にいたっては「空のオブジェクト(プロパティを持たないオブジェクト)」の意味だと思われがちですが、実際は「null と undefined 以外のあらゆる値」を表します。
つまり3つとも「オブジェクトを表す型」という直感とは裏腹に、それぞれ許可する値の範囲もプロパティアクセスの可否も異なります。まずは1つずつ、正確な仕様と実際の挙動を見ていきましょう。
小文字 object 型は「プリミティブ以外」を表す
小文字の object 型は、プリミティブ型(string・number・boolean・symbol・bigint・null・undefined)以外のすべてを表す型です。言い換えると「その値がプリミティブではない=何らかのオブジェクトである」ということだけを表現します。オブジェクトリテラル・配列・関数などは代入できますが、数値や文字列などのプリミティブは代入できません。
// オブジェクトである値は代入できる
const a: object = { name: 'Alice' }; // OK
const b: object = [1, 2, 3]; // OK(配列もオブジェクト)
const c: object = () => {}; // OK(関数もオブジェクト)
// プリミティブは代入できない
const d: object = 42; // エラー: 型 'number' を 'object' に割り当てできません
const e: object = 'hello'; // エラー: 型 'string' を 'object' に割り当てできません
const f: object = true; // エラー: 型 'boolean' を 'object' に割り当てできません
ここで注意したいのは、object 型は「オブジェクトであること」しか保証しないため、具体的なプロパティには一切アクセスできないという点です。次のように name プロパティを持つオブジェクトを代入しても、型としては「中身が分からないオブジェクト」なので、プロパティを読もうとするとエラーになります。
const user: object = { name: 'Alice', age: 30 };
// エラー: プロパティ 'name' は型 'object' に存在しません。
console.log(user.name);
// エラー: プロパティ 'age' は型 'object' に存在しません。
console.log(user.age);
そのため object 型は、「プリミティブは弾きたいが、中身の形は問わない」という限定的な場面(たとえば何らかのオブジェクトを受け取ってそのまま別の関数に渡すだけ、といったケース)で役立ちます。プロパティを使いたいなら、後述するように具体的な型や Record 型を選ぶ必要があります。
大文字 Object 型はプリミティブまで通ってしまう
大文字の Object は、JavaScript のグローバルな Object インターフェースを指す型です。これは toString() や valueOf() など Object.prototype のメソッドを持つ値を表しますが、JavaScript ではプリミティブも一時的にラッパーオブジェクトへ変換されてこれらのメソッドを呼べるため、number や string などのプリミティブまで代入できてしまいます(null と undefined だけは strictNullChecks 下で弾かれます)。
// オブジェクトはもちろん代入できる
const a: Object = { name: 'Alice' }; // OK
// プリミティブまで代入できてしまう(ここが object 型との大きな違い)
const b: Object = 42; // OK
const c: Object = 'hello'; // OK
const d: Object = true; // OK
// null と undefined は strictNullChecks 下ではエラー
const e: Object = null; // エラー
const f: Object = undefined; // エラー
「オブジェクトの型」という名前に反して、数値も文字列も通ってしまうため、Object 型を「オブジェクトだけを受け取りたい」という意図で使うのは危険です。また、代入できる値の範囲が広い一方で、アクセスできるのは Object.prototype が持つメソッドだけです。独自プロパティにはアクセスできません。
const value: Object = { name: 'Alice' };
// Object.prototype のメソッドは呼べる
console.log(value.toString()); // OK
console.log(value.hasOwnProperty('name')); // OK
// 独自プロパティにはアクセスできない
console.log(value.name); // エラー: プロパティ 'name' は型 'Object' に存在しません。
このように Object 型は「値の範囲は広すぎるのにプロパティはほぼ使えない」という中途半端な型で、実務でこれを明示的に書く理由はほとんどありません。TypeScript の公式ガイドラインでも、大文字の Object をアノテーションに使うことは推奨されていません。
空オブジェクト型 {} には 42 すら代入できる
{} は一見すると「プロパティを1つも持たないオブジェクト」を表すように見えます。しかし実際の意味は 「null と undefined 以外の任意の値」です。プロパティを持たないという制約ではなく、「少なくとも null・undefined ではない」という非常にゆるい制約でしかないため、オブジェクトはもちろん、数値や文字列などのプリミティブも代入できてしまいます。
// 「空のオブジェクト」のつもりでも、実際は何でも入る
const a: {} = { name: 'Alice' }; // OK
const b: {} = 42; // OK(驚くところ)
const c: {} = 'hello'; // OK
const d: {} = true; // OK
const e: {} = [1, 2, 3]; // OK
// 弾かれるのは null と undefined だけ
const f: {} = null; // エラー
const g: {} = undefined; // エラー
const b: {} = 42; が何のエラーもなく通ってしまうのは、多くの人が最初に驚くポイントです。{} は「プロパティが空のオブジェクト」ではなく「null/undefined を除くトップに近い型」だと理解しておくと、この挙動に納得できます。実質的な意味は大文字の Object とほぼ同じで、代入できる値の範囲も、プロパティアクセスができない点も共通しています。
もちろん {} に代入した値も、宣言した型は「空のオブジェクト」なので、具体的なプロパティにはアクセスできません。
const value: {} = { name: 'Alice' };
// エラー: プロパティ 'name' は型 '{}' に存在しません。
console.log(value.name);
3つの型を比較して整理する
ここまでの内容を、「代入できる値」「プロパティアクセス」「使いどころ」の観点で並べて整理します。Object と {} はほぼ同じ挙動で、object だけがプリミティブを弾くという違いに注目してください。
| 型 | 代入できる値 | プロパティアクセス | 使いどころ |
|---|---|---|---|
object(小文字) | プリミティブ以外のオブジェクト値のみ | 不可(中身の形は不明) | プリミティブを弾きつつ、中身を問わずオブジェクトを受けたいとき |
Object(大文字) | null・undefined 以外のほぼすべて(プリミティブ含む) | Object.prototype のメソッドのみ | 基本的に使わない(意図が伝わらず範囲も広すぎる) |
{}(空オブジェクト型) | null・undefined 以外の任意の値 | 不可(プロパティを持たない型) | 基本的に使わない(Object とほぼ同じで誤解を招く) |
表からわかるように、Object と {} は「null/undefined 以外なら何でも通る」という点で実質同じであり、どちらも「オブジェクトを受け取る型」としては役に立ちません。object はプリミティブを弾ける分マシですが、それでもプロパティにはアクセスできないため用途は限られます。
結局どれを使うべきか|any・unknown・Record との違い
「任意のオブジェクトを受け取りたい」という目的で本当に使うべき型は、多くの場合 object・Object・{} のいずれでもありません。ここで any・unknown・Record<string, unknown> との違いを踏まえて、用途ごとに適切な選択肢を確認します。
any との違い
any は「型チェックを完全に放棄する」型です。どんな値でも代入でき、どんなプロパティにも自由にアクセスできてしまうため、コンパイラのチェックが一切効きません。一方 object・Object・{} は、たとえプロパティにアクセスできなくても存在しないプロパティへのアクセスはエラーとして検出してくれます。「とりあえず何でも入れたい」ときに any を選ぶと型安全性を失うので、まずは any 以外を検討するのが基本方針です。
中身が未知の値には unknown
「オブジェクトかどうかも含めて中身がまったく分からない値」を受け取りたいなら unknown が適しています。unknown は any と同じくどんな値でも代入できますが、any と違ってそのまま使う前に型を絞り込む(型ガードする)ことが強制されるため、安全に扱えます。プリミティブも含めて何が来るか分からない外部入力(JSON.parse の結果や API レスポンスなど)には unknown が向いています。
function handle(input: unknown) {
// そのままではプロパティにアクセスできない(型を絞る必要がある)
if (typeof input === 'object' && input !== null && 'name' in input) {
// ここでは name を持つオブジェクトだと分かっている
console.log((input as { name: string }).name);
}
}
キーと値を持つオブジェクトには Record<string, unknown>
「文字列キーで値を持つオブジェクト」を受け取り、キーを列挙したり値を読んだりしたいなら、Record<string, unknown> が最も実用的です。これは「任意の文字列キーに対して、値の型が unknown」という意味で、キー経由でのアクセスが型として許されつつ、値は unknown なので使う前に型チェックが必要という、安全さと使いやすさのバランスが取れた型です。
// 文字列キーと unknown 値を持つオブジェクトを受け取る
function printEntries(obj: Record<string, unknown>) {
for (const key of Object.keys(obj)) {
// キー経由で値を読める(値の型は unknown なので安全)
console.log(key, obj[key]);
}
}
printEntries({ name: 'Alice', age: 30 }); // OK
printEntries(42); // エラー: 型 'number' を割り当てできません(プリミティブは弾かれる)
このように用途で選ぶと、指針はシンプルになります。中身の形が決まっているなら具体的な型(interface や type)を書く。プリミティブを弾いて「何らかのオブジェクト」だけ受けたいなら object。文字列キーでアクセスしたいなら Record<string, unknown>。オブジェクトかどうかも分からない未知の値なら unknown。Object と {} をアノテーションとして選ぶ場面は、実務ではほぼありません。
{} をオブジェクトのつもりで使うと危険
もっとも事故が起きやすいのが、{} を「空のオブジェクト、または何らかのオブジェクト」の意味で関数の引数に使ってしまうケースです。前述のとおり {} は null/undefined 以外の任意の値を許すため、オブジェクトだけを想定していても数値や文字列がそのまま通ってしまい、バグの温床になります。
// オブジェクトを受け取るつもりで {} を使ってしまった例
function save(data: {}) {
// data がオブジェクトである保証はない
console.log(data);
}
save({ id: 1 }); // 想定どおり
save(42); // エラーにならない(想定外だが通ってしまう)
save('hello'); // エラーにならない(同上)
「オブジェクトだけを受け取りたい」なら、少なくともプリミティブを弾く object を使うべきです。さらにキーや値を扱いたいなら Record<string, unknown> にすれば、プリミティブが弾かれるうえにプロパティアクセスも安全に行えます。{} を「空のオブジェクト型」と読み違えたまま使うと、コンパイラが本来検出できるはずの誤りを見逃してしまう点に注意してください。
なお、これらの紛らわしい型を明示的に使ってしまうミスは、ESLint の @typescript-eslint/no-empty-object-type(旧 ban-types)などのルールで機械的に検出できます。チーム開発では、こうしたルールを有効にしておくと Object や {} の誤用を未然に防げます。
まとめ
object(小文字)・Object(大文字)・{}(空オブジェクト型)は見た目こそ似ていますが、意味はまったく異なります。object は「プリミティブ以外のオブジェクト値」を表し、プリミティブを弾ける代わりにプロパティへはアクセスできません。Object は null/undefined 以外のほぼすべての値を受け付け、42 や 'hello' のようなプリミティブまで通ってしまうため、実務では使わないのが無難です。{} は「空のオブジェクト」ではなく「null/undefined 以外の任意の値」を表し、Object とほぼ同じ挙動で、やはり誤解を招くため避けるべきです。「任意のオブジェクトを受けたい」ときは、中身が決まっているなら具体的な型、プリミティブを弾きたいだけなら object、キーでアクセスしたいなら Record<string, unknown>、何が来るか分からない未知の値なら unknown を選ぶ、と用途で使い分けるのが正解です。型チェックを放棄する any は最後の手段と考えましょう。