How to Check if a Value is an Object in JavaScript
Tags: javascript
Published: 2023-11-10 22:46:06

There are several strategies to check if a value is an object. The primary reason is that in JavaScript, any non-primitive type (primitives: string, number, bigint, boolean, undefined, symbol, and null) is treated as an object, but depending on the checking method, there are considerations to take into account.


## Using `typeof` operator

JavaScript provides the typeof operator to check the value data type.

> The typeof operator returns a string indicating the type of the operand's value.

`typeof variable === 'object'` returns true for:

- objects
- null 😶
- arrays
- regular expressions
- new Date()
- new Map()
- new Set()

So these validation could be required.


`code`
```javascript
const isObject = (value) => {
  return typeof value === 'object'
  && value !== null
  && !Array.isArray(value)
  && !(value instanceof RegExp)
  && !(value instanceof Date)
  && !(value instanceof Set)
  && !(value instanceof Map)
}
```

## Using `instanceof` operator

Just like typeof, multiple validations may be necessary.

`variable instanceof Object` returns true for:

- functions
- arrays
- regular expressions
- new Date()
- new Map()
- new Set()

NOTE: `instanceof` returns false for objects initialised as `Object.create(null)` even after set additional properties.
(`Object.create(null)` is used explicitly when an object without prototype chain, any properties or methods from Object.prototype is needed) 

`code`
```javascript
const user = Object.create(null)
user.id = 'A0012'
const client = Object.create(user)
const isObject = user instanceof Object
const isClientObj = client instanceof Object
```

`test`
```javascript
test('user should be an object', () => {
 expect(isObject).toBeTruthy()
})

test('client should be an object', () => {
 expect(isClientObj).toBeTruthy()
})
```
❌ `FAIL` user should be an object
❌ `FAIL` client should be an object

## 💪 ✨ Object.prototype.toString.call()✨

This method returns the stringified type identifier, like `[object Object]`, `[object Array]`...

NOTE: `toString.call()` instead of `toString()` is used to prevent unexpected behaviour due `this`.

The advantage of this strategy is that it distinguishes all types with specificity, such as Array, RegExp, etc. and it doesn't throw Uncaught TypeError.

`code`
```javascript
const user = {}
const isObj = Object.prototype.toString.call(user) === '[object Object]'
```

`test`
```javascript
test('user should be an object', () => {
 expect(isObject).toBeTruthy()
})
```
✅ PASS user should be an object

The ability to return the specific type of the value allows for easy validation of other types, with the only observation that values created with a constructor and new keyword may behave differently than expected.

Ex: `Object.prototype.toString.call(new Number(3))`, `Object.prototype.toString.call(Number(3))`, and `Object.prototype.toString.call(3)` all return `'[object Number]'`.

## ✨ Using `constructor` property

> The constructor data property returns a reference to the constructor function that created the instance object

Similar to `Object.prototype.toString.call()` gives the chance to know the specific type

With three important observations:
- `variable.constructor === Object` returns `false` for `Object.create(null)`
- It throws Uncaught TypeError for `null` and `undefined`
- Since the constructor property can potentially be overwritten, like { constructor: 1 }, it may not be reliable.

Some extra validation should be necessary (This solution does not take into account the override of constructor property):

`code`
```javascript
const isObject = (value) => {
  return value != null // equality operator checks `undefined` and `null`
  && 
  (value.constructor === Object || (!value.constructor && typeof value === 'object'))
}
```

Note: To validate other types, the only caveat is the same as that of `Object.prototype.toString.call()`, concerning the type returned, whether the value is created as a primitive or using a constructor with the new keyword.

## Alternative approaches for validating object values

There are also other strategies to check if a value is an object like:

- `Object.prototype.valueOf.call(value))`:

_It throws Uncaught TypeError for null and undefined_
- `value.--proto--`:

_Deprecated_
- `Object.getPrototypeOf(value).isPrototypeOf(Object)`
- `Object.prototype.isPrototypeOf(value)`