Check if a value is an object in JavaScript

The snippet:

function isObject(maybeObject) {
	return Object.prototype.toString.call(maybeObject) === "[object Object]"
}

Problem

Let’s say you want to find out if a value is an object in JavaScript.

I used to do something like this:

function isObject(value) {
	if (typeof value === "object") {
		return true
	}

	return false
}

But here comes null and [] to cause trouble:

typeof null === "object" // -> true
typeof [] === "object" // -> true

Alright, let’s deal with those. Maybe we could get away with something like this:

function isObject(maybeObject) {
	if (typeof maybeObject === "object" && value !== null && !Array.isArray(maybeObject)) {
		return true
	}

	return false
}

But then more troublemakers appear:

typeof new Date() === "object" // -> true
typeof Math === "object" // -> true
typeof JSON === "object" // -> true

A solution

function isObject(maybeObject) {
	return Object.prototype.toString.call(maybeObject) === "[object Object]"
}

Using Object.prototype.toString in this way comes quite in handy here since it allows us to use the toString() like a utility function.

How does it look when we don’t pass an object?

Object.prototype.toString.call('hello world')   // -> "[object String]"
Object.prototype.toString.call(2)               // -> "[object Number]"
Object.prototype.toString.call([])              // -> "[object Array]"
Object.prototype.toString.call(null)            // -> "[object Null]"
Object.prototype.toString.call(JSON)            // -> "[object JSON]"

A little extra for TypeScript

For TypeScript we could add some extra niceties:

function isObject(maybeObject: unknown): maybeObject is Record<string, any> {
	return Object.prototype.toString.call(maybeObject) === "[object Object]"
}

For type safety we could declare that maybeObject is of type unknown. We don’t know what the caller will pass in.

The second part; maybeObject is Record<string, any> is a type predicate to say what the type should be if the function returns true. Without it the thing we pass in will retain its type - no bueno:

// isObject WITHOUT maybeObject is Record<string, any>
function someOperation(data: unknown) {
	isObject(data) {
		data // <- Type is unknown. We would have to narrow it down here to work with it.
	}
}

// isObject WITH `maybeObject is Record<string, any>`
function someOperation(data: unknown) {
	isObject(data) {
		data // <- Type is Record<string, any> within the closure.
	}
}