JavaScript array type check - “is array” vs object in-depth
Detecting Array vs Object in JavaScript with examples
tl;dr To detect if something is an Array in JavaScript, use Array.isArray(somethingObjectToCheck)
.
This post is a quarter useful, quarter history lesson, half quirky JavaScript exploration.
Table of contents:
Table of Contents
Array.isArray behaviour with different types
Examples for this section at observablehq.com/@hugodf/javascript-array-detection-using-array-isarray, you can play around with the values
list if anything is missing (click here to do that).
See below the full table for values for which Array.isArray
evaluates to true
for. Which is to say Array.isArray
works, since it’s only true for Arrays, and it’s true for any Array.
No matter how it’s initialised and whether or not it’s been initialised using the local window’s Array or another one’s.
For people who are here to know how to detect if an object is an Array: use Array.isArray
.
That’s it, off you go and solve problems.
For those who want some extra spicy JavaScript coding and want to understand what makes a JavaScript Array an Array you read on or
skip to where, we’re going to re-implement isArray
.
Type | Value as JSON | Array.isArray |
---|---|---|
Empty Array | [] | true |
Array of Number literal | [1,2,3] | true |
Array of String literal | [“a”,“b”,“c”] | true |
Array (using constructor.from) | [null,null,null,null,null,null,null,null,null,null] | true |
Array with empty values using constructor | [null,null,null,null,null,null,null,null,null,null] | true |
Array with values using constructor | [1,2,3] | true |
Array prototype | [] | true |
Array from other window | [1,2,3] | true |
Empty Object | {} | false |
Object with .length property | {“length”:10} | false |
Object whose __prototype__ is Array | {} | false |
Object | {“a”:“2”,“b”:“c”} | false |
Object with nested Array | {“nested”:[“2”,“c”]} | false |
Empty Set | {} | false |
Set of String | {} | false |
Set of Number | {} | false |
Empty Map | {} | false |
Map with different types | {} | false |
String literal | “hello world” | false |
String Object | “hello world” | false |
Boolean true literal | true | false |
Boolean falst literal | false | false |
Explicity undefined | undefined | false |
Implicit undefined | undefined | false |
null | null | false |
Number (integer) | 123 | false |
Number (float) | 1.23 | false |
What’s so special about Array.isArray
?
Also known as the “why don’t we just use instanceof Array
?” problem.
When checking for
Array
instance,Array.isArray
is preferred overinstanceof
because it works throughiframes
. See the MDN section/example
Or if you prefer the MIT explanation:
[…] by the semantics of
instanceof
,o instanceof Array
works correctly only ifo
is an array created by that page’s originalArray
constructor (or, equivalently, by use of an array literal in that page).See Determining with absolute accuracy whether or not a JavaScript object is an array
In plain English:
In JavaScript, the Array constructor/prototype is not shared across windows/iframes. Therefore instanceof Array
works, but only if you’re never going to share Array
s across windows/iframes, since at that point, they won’t have the same constructor/prototype.
Implementation options
There’s a full set of implementation attempts and their tabulated output at https://beta.observablehq.com/@hugodf/array-isarray-implementations.
We take some of the approaches from “Determining with absolute accuracy whether or not a JavaScript object is an array” plus the official MDN polyfill for a spin.
The official polyfill
See the MDN docs or check it out in the Observable notebook:
function isArrayMdnOfficial(objToCheck) {
return Object.prototype.toString.call(objToCheck) === '[object Array]';
}
This 100% works but feels a bit dirty, the only thing to be weary of is:
“
Object.prototype.toString
andFunction.prototype.call
not being changed (probably a good assumption but still fragile)”– J Walden - Determining with absolute accuracy whether or not a JavaScript object is an array
This hack is the only way to do this check properly, as we will see.
The duck-typing approach
The duck-typing approach, if it behaves like an Array, then it’s an Array, ie. see if stuff we could expect on an Array to be there.
This could be pushed to an extreme and check for every Array method.
Positive: anything that behaves like an Array would work.
Negative: anything that behaves like an Array would work.
In Computer Science-speak: anything implementing the JavaScript Array interface (or a subset that we’re actually testing for) would work.
Final negative that means we’re not going to use this: it’s ugly, doesn’t scale… And we’ve got native Array.isArray
which pretends like it’s a clean function when actually it just toString
-s stuff.
function isArrayLengthAndPushCheck(objToCheck) {
return Boolean(objToCheck) && objToCheck.length != null && typeof objToCheck.push === 'function';
}
Checking the prototype/constructor/instance type
We get the following two functions.
Check the constructor
function isArrayConstructor(objToCheck) {
return Boolean(objToCheck) && objToCheck.constructor === Array;
}
Use instanceof
function isArrayInstanceOf(objToCheck) {
return objToCheck instanceof Array;
}
Here’s the ugly output table for these:
Label | Array.isArray | isArrayConstructor | isArrayInstanceOf |
---|---|---|---|
Empty Array | true | true | true |
Array of Number literal | true | true | true |
Array of String literal | true | true | true |
Array (using constructor.from) | true | true | true |
Array with empty values using constructor | true | true | true |
Array with values using constructor | true | true | true |
Array prototype | true | true | false |
Array from other window | true | false | false |
Object whose __prototype__ is Array | false | true | true |
Object with nested Array | false | false | false |
These all fail the test when an Array is from another window and Object
s masquerading as Array
s. The instanceof
approach also fails on the constructor (which is an Array) since it’s not an instance of itself.
That’s why MDN predicates using the polyfill which uses the nasty toString
call.
For more JavaScript nasties 🍬 and spicies 🌶:
Subscribe to the Code with Hugo newsletter.
Array vs Object: application
Let’s say we want to measure the depth of we have an object with mixed nested arrays/objects like so:
const obj = {
myKey: {
nest: {
doubleNested: 'value',
nestedArray: [ { key: 'value' } ]
}
}
};
The difficulty lies in detecting whether we should treat the value as an object (dictionary) or as a list.
To break down object vs primitive type detection, it’s a case of typeof obj === 'object'
, see this quick reminder of types of things:
console.assert(typeof '', 'string');
console.assert(typeof new String(), 'string');
console.assert(typeof 1, 'number');
console.assert(typeof Infinity, 'number');
console.assert(typeof NaN, 'number');
console.assert(typeof undefined, 'undefined');
console.assert(typeof [], 'object');
console.assert(typeof null, 'object');
console.assert(typeof {}, 'object');
console.assert(typeof new Map(), 'object');
console.assert(typeof new Set(), 'object');
Now to separate Objects vs Arrays it’s Array.isArray
every day (see above):
// Console.assert flips out again
// even though the assertions hold
console.assert(Array.isArray({}), false);
console.assert(Array.isArray(new Map()), false);
console.assert(Array.isArray(new Set()), false);
console.assert(Array.isArray([]), true);
console.assert(Array.isArray(new Array()), true);
That means we should be able to implement maxDepth
as:
function maxDepth(obj, depth = 0) {
if (typeof obj !== 'object') {
return depth;
}
const [values, depthIncrease] = Array.isArray(obj)
? [obj, 0]
: [Object.values(obj), 1];
return values.length > 0
? Math.max(...values.map(
value => maxDepth(value, depth + depthIncrease))
)
: depth;
}
Some of these fail even though the assertions hold 🙄:
console.assert(maxDepth({}), 0);
console.assert(maxDepth(''), 0);
console.assert(maxDepth([ { one: 'deep' } ]), 1);
console.assert(maxDepth({ one: 'deep' }), 1);
console.assert(maxDepth({ one: [ { two: 'deep' } ] }), 2)
console.assert(maxDepth({ one: { two: 'deep' } }), 2)
There we go: a practical application of Array.isArray
Get The Jest Handbook (100 pages)
Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.
orJoin 1000s of developers learning about Enterprise-grade Node.js & JavaScript