🙄

【TS】オブジェクト型を判定する

に公開
6

概要

Typescriptである値がオブジェクト型かどうかを判定する際に単純にtypof arg ==== 'object'だけをしていたので備忘として整理しました。

有識者の方にいろいろアドバイスをいただきました!ありがとうございます!

結論

今回判定したいオブジェクトは、

  • オブジェクトリテラル
  • 自作クラスで作成
  • new
const isObject = (arg:any): arg is object => {
    if (typeof arg !== 'object' || arg === null) return false;
    if (Object.prototype.toString.call(arg) !== '[object Object]')  return false;
    const proto = Object.getPrototypeOf(arg);
    return proto === null || proto === Object.prototype;
}

解説

typeof arg === 'object'

対象の値の型がobjectかどうかを判定。ただ実はこれnullや配列、new DateのようなJavascriptの組み込みオブジェクトまでtrueになる。
要件によってはむしろこれでOKってパターンがあるかもだけど、今回オブジェクト判定して欲しいのは以下のようなキーと値から構成される純粋なオブジェクトだけに絞りたい。だからtypeof arg === 'object'だけだと想定外のものもtrueになってしまう。

/// true判定したい純粋オブジェクト
const test = {
    name:"Bob",
    age:30
}

// false判定して欲しいもの
const arr = [](配列)
const date = new Date();(組み込み関数)
const funct = () => {}; (関数)

arg.constructor === Object

.constructorプロパティは、対象のオブジェクトがどのコンストラクタ関数から生成されたかを示す。arg.constructor === ObjectObject型のコンストラクタ関数から生成されているかどうかを判定している。

arg.constructor === Objectだけでよくない?

と、思ったのですが、これだけだと純粋なオブジェクト判定はできない模様。なので、各条件が除外するものを一覧にまとめました。

条件 除外するもの
typeof arg === 'object' プリミティブ型(number, string, boolean, symbol, undefined)を除外
arg !== null nullを除外
arg.constructor === Object 配列、組込オブジェクト(Date, RegExpなど)、関数(function)を除外

Discussion

junerjuner

それだと

{
  __proto__:null
}

みたいな純粋オブジェクトが判定できないのでは……?
とも思ったが、 structuredClone とかでは維持されないし、ままいいか……。

1
noelsunnoelsun

ありがとうございます!たしかに上記の考慮が足りていませんでした...!
ちょっと脱線してしまうのですが、上記のようなプロトタイプチェーンを意図的に切るオブジェクトってどのような時に使われますか?

junerjuner

__proto__ プロパティに意味を持たせたくないときとかでしょうか……。
Object.create(null) と同様に __proto__ を設定してもただの通常プロパティと同じ扱いになり、プロトタイプ汚染攻撃対策となります。

noelsunnoelsun

ありがとうございます!「プロトタイプ汚染攻撃対策」を恥ずかしながら知りませんでした。

{__protop__:null}

上記のようなオブジェクトを含めて、オブジェクト判定するロジックを検討してみました!もしよければご確認とご助言をいただけますと幸いです。

const isObject = (arg:any): arg is object => {
    if (typeof arg !== 'object' || arg === null) return false;

    if (Object.prototype.toString.call(arg) !== '[object Object]')  return false;
    
    const proto = Object.getPrototypeOf(arg);
    return proto === null || proto === Object.prototype;
}

1
junerjuner

いいと思いますー。

テスト
const entries: {
  input: any,
  output: boolean;
}[] = [
  {
    input: {},
    output: true,
  },
  {
    input: [],
    output: false,
  },
  {
    input: { __proto__: null },
    output: true,
  },
  {
    input: new (class { }),
    output: false,
  },
  {
    input: { __proto__: Error },
    output: false,
  },
  {
    input: true,
    output: false,
  },
  {
    input: 1,
    output: false,
  }
];

for (const [index, {input, output}] of entries.entries()) {
  if (isObject(input) === output) continue;
  console.error(`index:${index} result not ${output} (input:${typeof input})`);
}

https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAlhA8gIwFYFNiwLwwBQCGATgOYBcMBYAngJQXEnwQwhqY4B8MA3gFAx4AM3xRqAB3QgRjGAEJsuAORsMWJTAA+myqRiLcYAK4AbE7RhF0UI0TAwhBExHQBuPgOH4UaqADpxIhAoYIl0PxCAZSgiODASP2AnE0JSCwVlAG1VDhgfDgBdJQsrGzsHJxd3T1BIWEDgkH089iw-EmsABSCQsUlEIVSSWndBUtt7BpD9RRhjMy0dKaaDFt8AntDJdwBfD1roGHQwGLh0CAp+QTjxIygGGgAaTxA72-uYZBAQE3QqXcyBWamU8V0E8DA70uO2e4NYbzuFBiRnQsJgMNBnmukMRMEBaMErygUIqzlRngxgjB2JJPBgAH16ctGRR5iZ0QT4cTccjyYJKbwsRCSWB0AB3fDAEwECAsOk7WicokkxxktEC6nC3F0xnM+kUACiRCCRA5QuVuNVLnVaM1Nx5RBRSoRHytfLNVKF9o+AEZndzXZV3XsCtUhCBTXgDrBMnEACboAAej143pTFqgOyB0iOJ1i5z8x1O5zwtAsYLgIjwCHyWGrOKgFlWGYstSgcRRoxgBx+4XQxojeAABvGk2QACQ8UeJnaWc6mWBgYIwScZ2f1qGTvpSETehVDkZ8HZAA

1
noelsunnoelsun

ご丁寧にいろいろありがとうございます!!!