「オブジェクト判定」とは何を見分けたいのか
「オブジェクト判定」と聞くと、
「typeof value === 'object' って書けばいいんでしょ?」と思いがちですが、実務ではそれだと足りません。
JavaScript で「オブジェクト」と言ったときに、実際にはいくつかの種類があります。
- プレーンなオブジェクト(
{}や{ id: 1 }) - 配列(
[]や[1, 2]) - 日付オブジェクト(
new Date()) - 正規表現(
/abc/) - そして、なぜか
nullもtypeof null === 'object'
業務で「オブジェクト判定」をしたいとき、多くの場合は
「プレーンなオブジェクトかどうかを知りたい」「配列は除外したい」「null はもちろん除外したい」
というニーズが多いです。
なので、「ただの typeof では足りない」「何を“オブジェクトとみなすか”をはっきり決める」ことが、最初の一歩になります。
typeof の落とし穴をちゃんと理解する
typeof だけでは危ない理由
まずは、素直に typeof を試してみます。
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof null); // "object"
console.log(typeof new Date()); // "object"
JavaScript全部 "object" です。
つまり、typeof value === "object" だけでは、「配列かもしれないし、null かもしれないし、Date かもしれない」という曖昧な判定にしかなりません。
初心者がよくやってしまうのが、これです。
if (typeof value === "object") {
// ここで「オブジェクトだからプロパティが読めるはず」と思ってしまう
console.log(value.id);
}
JavaScriptvalue が null のとき、このコードは null.id を読もうとしてエラーになります。
「typeof で object だから安心」と思った瞬間にハマる典型パターンです。
「オブジェクトかどうか」のレベルを分けて考える
実務での「オブジェクト判定」は、ざっくり次の 3 段階に分けて考えると整理しやすくなります。
- 「null ではなく、オブジェクト型かどうか」
- 「配列ではないかどうか」
- 「“プレーンなオブジェクト”だけに絞りたいかどうか」
この 3 段階を意識すると、「自分が今ほしいのはどのレベルの判定か」がはっきりします。
レベル 1: 「null ではないオブジェクトかどうか」
まずは、「null を除外したオブジェクト判定」です。
function isObjectLike(value) {
return value !== null && typeof value === "object";
}
console.log(isObjectLike({})); // true
console.log(isObjectLike([])); // true
console.log(isObjectLike(new Date())); // true
console.log(isObjectLike(null)); // false
console.log(isObjectLike(123)); // false
JavaScriptここでのポイントは、value !== null を必ず先に見ることです。typeof null === "object" という歴史的な仕様のせいで、null を除外しないと「オブジェクトっぽいもの」が混ざってしまいます。
isObjectLike は、「配列や Date も含めて、“オブジェクトっぽいもの全般”」を判定したいときに使えます。
例えば、「JSON で返ってきたデータが、プリミティブ(数値や文字列)ではなく、何かしらの構造を持った値かどうか」をざっくり見たいときなどです。
レベル 2: 「配列を除外したオブジェクト判定」
次に、「配列は除外したい」というパターンです。
業務では、「ここでは配列ではなく、キーと値のペアを持つオブジェクトだけを扱いたい」という場面が多いです。
function isNonArrayObject(value) {
return value !== null
&& typeof value === "object"
&& !Array.isArray(value);
}
console.log(isNonArrayObject({})); // true
console.log(isNonArrayObject({ id: 1 })); // true
console.log(isNonArrayObject([])); // false
console.log(isNonArrayObject(null)); // false
console.log(isNonArrayObject(123)); // false
JavaScriptここでの重要ポイントは、Array.isArray を必ず使うことです。typeof では配列とオブジェクトを区別できないので、「配列を除外したい」と決めた瞬間に Array.isArray が必須になります。
このレベルの判定は、「設定オブジェクト」「検索条件オブジェクト」「パラメータオブジェクト」など、
「キーと値のペアを持つ“普通のオブジェクト”だけを受け付けたい」場面でよく使います。
レベル 3: 「プレーンなオブジェクトだけを判定したい」
さらに一歩踏み込んで、「クラスのインスタンスや Date などは除外して、“素の {} から作られたオブジェクト”だけを判定したい」というニーズもあります。
例えば、次のようなケースです。
class User {
constructor(id) {
this.id = id;
}
}
const plain = { id: 1 };
const user = new User(1);
const date = new Date();
JavaScriptここで「プレーンなオブジェクトだけを許可したい」なら、plain は OK で、user や date は NG にしたいわけです。
function isPlainObject(value) {
if (value === null || typeof value !== "object") return false;
if (Array.isArray(value)) return false;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
console.log(isPlainObject({})); // true
console.log(isPlainObject({ id: 1 })); // true
console.log(isPlainObject([])); // false
console.log(isPlainObject(new Date())); // false
console.log(isPlainObject(new (class {})())); // false
JavaScriptここでやっていることは、「プロトタイプ(継承元)が Object.prototype か null かどうか」を見ています。
これにより、「クラスインスタンスや特殊なオブジェクト」を除外し、「素のオブジェクト」だけを判定できます。
実務では、「API のパラメータはプレーンなオブジェクトだけにしたい」「設定ファイルはプレーンなオブジェクトだけを許可したい」といった場面で役に立ちます。
どのレベルの判定をユーティリティにするか
ここまでの 3 レベルを整理すると、こうなります。
isObjectLike
null ではなく、typeof が “object” のもの(配列や Date も含む)isNonArrayObject
null ではなく、typeof が “object” で、配列ではないものisPlainObject
プレーンなオブジェクト({}ベース)だけ
プロジェクトの性格によって、「どこまでを“オブジェクト”と呼ぶか」は変わります。
なので、業務ユーティリティとしては、次のように名前を分けておくのがおすすめです。
// ざっくり「オブジェクトっぽいもの全般」
function isObjectLike(value) { ... }
// 配列を除外した「オブジェクト」
function isObject(value) { ... }
// プレーンなオブジェクトだけ
function isPlainObject(value) { ... }
JavaScriptこうしておくと、コードを読む人が「ここではどのレベルのオブジェクトを期待しているのか」を一瞬で理解できます。
実務での具体的な利用イメージ
API のパラメータ検証
例えば、「検索条件をオブジェクトで受け取る API クライアント」を考えます。
function searchUsers(conditions) {
if (!isPlainObject(conditions)) {
throw new Error("検索条件はプレーンなオブジェクトで指定してください");
}
// ここから下は「conditions はプレーンなオブジェクト」と信じて書ける
if (conditions.name) {
// ...
}
}
JavaScriptここで isPlainObject を使っておくことで、「配列や Date、クラスインスタンスなどが紛れ込む」ことを防げます。
バグが起きたときも、「そもそも引数の型がおかしい」ということにすぐ気づけます。
設定オブジェクトのマージ
設定オブジェクトをマージするユーティリティを書くときも、「オブジェクトかどうか」の判定が重要になります。
function mergeConfig(defaults, overrides) {
if (!isPlainObject(defaults)) {
throw new Error("defaults はプレーンなオブジェクトである必要があります");
}
if (!isPlainObject(overrides)) {
return defaults; // 上書き設定がなければ defaults のまま
}
return { ...defaults, ...overrides };
}
JavaScriptここで isPlainObject を使っておくと、「overrides に配列や数値が渡ってきたときに、静かに壊れる」のを防げます。
小さな練習で感覚をつかむ
最後に、手を動かして慣れるためのミニ課題を提案します。
自分で isObjectLike, isNonArrayObject, isPlainObject を実装して、次の値を片っ端から試してみてください。
{}{ id: 1 }[]new Date()null123"abc"new (class User {})()
それぞれの関数が true / false をどう返すかをコンソールに出してみると、
「自分が今ほしいのはどのレベルの“オブジェクト判定”なのか」が、かなりクリアに見えてきます。
