JavaScript Tips | 基本・共通ユーティリティ:型チェック – オブジェクト判定

JavaScript JavaScript
スポンサーリンク

「オブジェクト判定」とは何を見分けたいのか

「オブジェクト判定」と聞くと、
typeof value === 'object' って書けばいいんでしょ?」と思いがちですが、実務ではそれだと足りません。

JavaScript で「オブジェクト」と言ったときに、実際にはいくつかの種類があります。

  • プレーンなオブジェクト({}{ id: 1 }
  • 配列([][1, 2]
  • 日付オブジェクト(new Date()
  • 正規表現(/abc/
  • そして、なぜか nulltypeof 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);
}
JavaScript

valuenull のとき、このコードは null.id を読もうとしてエラーになります。
「typeof で object だから安心」と思った瞬間にハマる典型パターンです。


「オブジェクトかどうか」のレベルを分けて考える

実務での「オブジェクト判定」は、ざっくり次の 3 段階に分けて考えると整理しやすくなります。

  1. 「null ではなく、オブジェクト型かどうか」
  2. 「配列ではないかどうか」
  3. 「“プレーンなオブジェクト”だけに絞りたいかどうか」

この 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 で、userdate は 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.prototypenull かどうか」を見ています。
これにより、「クラスインスタンスや特殊なオブジェクト」を除外し、「素のオブジェクト」だけを判定できます。

実務では、「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()
  • null
  • 123
  • "abc"
  • new (class User {})()

それぞれの関数が true / false をどう返すかをコンソールに出してみると、
「自分が今ほしいのはどのレベルの“オブジェクト判定”なのか」が、かなりクリアに見えてきます。

タイトルとURLをコピーしました