How to efficiently check if variable is Array or Object (in NodeJS & V8)?
Asked Answered
P

15

71

Is there any way to efficiently check if the variable is Object or Array, in NodeJS & V8?

I'm writing a Model for MongoDB and NodeJS, and to traverse the object tree I need to know if the object is simple (Number, String, ...) or composite (Hash, Array).

It seems that V8 has fast built-in Array.isArray, but how to check if object is an Object? I mean complex object like hash {} or instance of class, not something like new String()?

Usually it may be done as this:

Object.prototype.toString.call(object) == "[object Object]"

or this:

object === Object(object)

But it seems that this operations aren't cheap, maybe there's some more efficient? It's ok if it's not universal and doesn't works on all engines, I need it only to work on V8.

Papyrus answered 12/1, 2012 at 11:12 Comment(5)
Thanks for help, by the way the model itself is here alexeypetrushin.github.com/mongo-model/presentations/…Papyrus
Both of those operations should be quite cheap. If you want to know if something can be used as an object (can get/set properties, etc.) use x === Object(x) but if you want more fine-grained testing, use Object.prototype.toString.call(x). Don't worry about the speed of either one, not without profiling it first.Excellence
Is there a reason typeof x is a poor choice? It will return 'string' for a string and 'object' for an object.Lucielucien
typeof [] returns "object"Eldred
Possible duplicate of Check if a value is an object in JavaScriptAgree
Y
27

All objects are instances of at least one class – Object – in ECMAScript. You can only differentiate between instances of built-in classes and normal objects using Object#toString. They all have the same level of complexity, for instance, whether they are created using {} or the new operator.

Object.prototype.toString.call(object) is your best bet to differentiate between normal objects and instances of other built-in classes, as object === Object(object) doesn't work here. However, I can't see a reason why you would need to do what you're doing, so perhaps if you share the use case I can offer a little more help.

Yulan answered 12/1, 2012 at 11:24 Comment(2)
Thanks for help, models are schema-free and can be nested - and in order to perform validations and conversions I need to traverse this object tree (without schema).Papyrus
@AlexeyPetrushin: so, presumably, there's a danger of some class instances being passed to your traversal function? It may be worth noting that all native objects and their native properties/methods are non-enumerable, which means you can't traverse them anyway.Yulan
M
135

For simply checking against Object or Array without additional function call (speed).

isArray()

let isArray = function(a) {
    return (!!a) && (a.constructor === Array);
};
console.log(isArray(        )); // false
console.log(isArray(    null)); // false
console.log(isArray(    true)); // false
console.log(isArray(       1)); // false
console.log(isArray(   'str')); // false
console.log(isArray(      {})); // false
console.log(isArray(new Date)); // false
console.log(isArray(      [])); // true

isObject()

let isObject = function(a) {
    return (!!a) && (a.constructor === Object);
};
console.log(isObject(        )); // false
console.log(isObject(    null)); // false
console.log(isObject(    true)); // false
console.log(isObject(       1)); // false
console.log(isObject(   'str')); // false
console.log(isObject(      [])); // false
console.log(isObject(new Date)); // false
console.log(isObject(      {})); // true
Millhon answered 17/5, 2013 at 11:38 Comment(8)
Why not use return (a)&&(a.constructor === Array)? @MillhonBalch
From what I can check: (a) && (a.constructor === Array) won't work if a=null...Balch
Right, if a is undefined or null then undefined or null will be returned. Though they are falsy values and would work in if statements... in the case of the example undefined or null would be logged instead of true or false.Importunacy
It will fail if we the object is getting created from a user defined constructor function. for example, var user = new User(). in this scenario, user.constructor will be "User" and not "Object"Michal
Doesn't work for custom classesIndignity
Thank you! Here's my ES11 modified shorthand for testing if value is a plain object: const isObject = (obj) => (obj ?? false)?.constructor?.name === "Object"; Works great in NodeJS.Ansley
does not work for isObject(new Error())Pieper
const isObject = a => a?.constructor === Object;Pebrook
Y
27

All objects are instances of at least one class – Object – in ECMAScript. You can only differentiate between instances of built-in classes and normal objects using Object#toString. They all have the same level of complexity, for instance, whether they are created using {} or the new operator.

Object.prototype.toString.call(object) is your best bet to differentiate between normal objects and instances of other built-in classes, as object === Object(object) doesn't work here. However, I can't see a reason why you would need to do what you're doing, so perhaps if you share the use case I can offer a little more help.

Yulan answered 12/1, 2012 at 11:24 Comment(2)
Thanks for help, models are schema-free and can be nested - and in order to perform validations and conversions I need to traverse this object tree (without schema).Papyrus
@AlexeyPetrushin: so, presumably, there's a danger of some class instances being passed to your traversal function? It may be worth noting that all native objects and their native properties/methods are non-enumerable, which means you can't traverse them anyway.Yulan
M
21

If its just about detecting whether or not you're dealing with an Object, I could think of

Object.getPrototypeOf( obj ) === Object.prototype

However, this would probably fail for non-object primitive values. Actually there is nothing wrong with invoking .toString() to retreive the [[cclass]] property. You can even create a nice syntax like

var type = Function.prototype.call.bind( Object.prototype.toString );

and then use it like

if( type( obj ) === '[object Object]' ) { }

It might not be the fastest operation but I don't think the performance leak there is too big.

Mellifluent answered 12/1, 2012 at 11:26 Comment(4)
+1, though bind actually makes it a lot slower, which is a shame because it's something that seems like it could be optimized much better under the hood.Yulan
They are pretty fast, but still 10 times slower than Array.isArray.Cratch
@AndyE: wut is going on, I wasn't aware that a bound method is almost 30% slower jsperf.com/bound-object-prototype-tostring-vs-unbound. Any explanation ? Esailija: true, isArray cannot get beat, but I guess OP wanted to check for an object instance explicitly.Mellifluent
@jAndy: I wish I knew, I can only speculate and hope that it is poorly implemented and unoptimized in the majority of JS engines. If that's the case then it will probably be improved at some point.Yulan
P
15

underscore.js is using the following

toString = Object.prototype.toString;

_.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };

_.isObject = function(obj) {
    return obj === Object(obj);
  };

_.isFunction = function(obj) {
    return toString.call(obj) == '[object Function]';
  };
Pulcheria answered 4/4, 2012 at 2:39 Comment(4)
WOuldn't Object(obj) clone the entire object? What if it's very large? Seems like a horrible way of checking to me!Eulaliaeulaliah
Probably instead of obj === Object(obj); it could be obj.constructor === Object.prototype.constructorIceberg
@Eloff: No, according to the ECMAScript 5 spec, this is what happens if you call Object on something that's already an object: The result is the input argument (no conversion). That's why it's === to the original object. So it should be very fast.Excellence
@Alex Yaroshevich: That's incorrect. That will return false on anything that isn't a direct instance of Object, which means it returns false on Arrays, Functions, RegExps, custom objects, etc.Excellence
K
14

I use typeof to determine if the variable I'm looking at is an object. If it is then I use instanceof to determine what kind it is

var type = typeof elem;
if (type == "number") {
    // do stuff
}
else if (type == "string") {
    // do stuff
}
else if (type == "object") { // either array or object
    if (elem instanceof Buffer) {
    // other stuff
Kahlil answered 12/1, 2012 at 11:25 Comment(6)
I noticed somebody just came along and downvoted all answers. Anyone care to explain why?Kahlil
Since this question is specifically for V8 & NodeJs, then this is actually the best (correct) answer afaict.... Use "instanceof Array"Bremsstrahlung
typeof returns "object" for null, and I am sure it has other problems.Surfperch
null is technically the correct way to represent a "missing" object. Having typeof null == 'object' is actually fine in my opinion. In the same manner typeof NaN == 'number' and typeof undefined == 'undefined'.Kahlil
Be careful when using typeof because typeof new String("foo") will return "object" instead of "string". Same goes for typeof null, typeof new Number(), typeof new Boolean(), etc… They all return "object".Homoeroticism
Well yes, but technically (javascript wise) those are objects :) If you want to check the object's type, use instanceof -> new String("foo") instanceof String will return true.Kahlil
B
12

Hi I know this topic is old but there is a much better way to differentiate an Array in Node.js from any other Object have a look at the docs.

var util = require('util');

util.isArray([]); // true
util.isArray({}); // false

var obj = {};
typeof obj === "object" // true
Buttermilk answered 29/9, 2013 at 8:0 Comment(2)
This is deprecated in Node.jsDow
Yes since Node 5+ has access to many standardised es6 features like Array.isArray(obj). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Buttermilk
T
10

Just found a quick and simple solution to discover type of a variable.

ES6

export const isType = (type, val) => val.constructor.name.toLowerCase() === type.toLowerCase();

ES5

function isType(type, val) {
  return val.constructor.name.toLowerCase() === type.toLowerCase();
}

Examples:

isType('array', [])
true
isType('array', {})
false
isType('string', '')
true
isType('string', 1)
false
isType('number', '')
false
isType('number', 1)
true
isType('boolean', 1)
false
isType('boolean', true)
true

EDIT

Improvment to prevent 'undefined' and 'null' values:

ES6

export const isType = (type, val) => !!(val.constructor && val.constructor.name.toLowerCase() === type.toLowerCase());

ES5

function isType(type, val) {
  return !!(val.constructor && val.constructor.name.toLowerCase() === type.toLowerCase());
}
This answered 4/4, 2017 at 11:51 Comment(0)
A
7

If you know that a parameter will definitely be either an array or an object, it may be easier to check for an array compared to checking for an object with something like this.

function myIsArray (arr) {
    return (arr.constructor === Array);
}
Affinal answered 11/7, 2017 at 17:6 Comment(0)
A
3

The Best Way I Can Use My Project.Use hasOwnProperty in Tricky Way!.

var arr = []; (or) arr = new Array();
var obj = {}; (or) arr = new Object();

arr.constructor.prototype.hasOwnProperty('push') //true (This is an Array)

obj.constructor.prototype.hasOwnProperty('push') // false (This is an Object)
Anderegg answered 23/8, 2016 at 8:23 Comment(0)
K
3

I know it's been a while, but I thought i would update the answer since there are new (faster and simpler) ways to solve this problem. Since ECMAscript 5.1 yo can use the isArray() method avaiable in the Array class.

Yo can see it's documentation in MDN here.

I think you shouldn't have a compatibility problem nowadays, but just in case, if you add this to your code you should be always safe that Array.isArray() is polyfilled:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}
Kiely answered 21/11, 2017 at 14:53 Comment(1)
Good solution, ECMAscript5.1 come since June 2011, so now almost all users should have a compatible browserMalayopolynesian
N
1

looking at jQuery they in there jQuery.isArray(...) they do:

    isArray = Array.isArray || function( obj ) {
    return jQuery.type(obj) === "array";
}

this leads us to: jQuery.type:

    type = function( obj ) {
    return obj == null ?
        String( obj ) :
        class2type[ toString.call(obj) ] || "object";
}

and again we have to look in: class2type

class2type = {};

// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

and in native JS:

var a, t = "Boolean Number String Function Array Date RegExp Object".split(" ");
for( a in t ) {
    class2type[ "[object " + t[a] + "]" ] = t[a].toLowerCase();
}

this ends up with:

var isArray = Array.isArray || function( obj ) {
    return toString.call(obj) === "[object Array]";
}
Norvun answered 12/1, 2012 at 11:26 Comment(0)
F
1

If you're sure that the variable you're checking will be either an object or an array, you could use the length property as well.

variable.length on an array will return an integer between 1 and n whereas variable.length on an object will return undefined.

If you have other data types in the mix then you can add separate checks for those.

Frothy answered 20/9, 2021 at 9:31 Comment(0)
S
0

I've used this function to solve:

function isArray(myArray) {
    return myArray.constructor.toString().indexOf("Array") > -1;
}
Spiritual answered 27/4, 2015 at 14:27 Comment(0)
R
0

Making - Object.isObject()

Object.prototype.isObject = function (param) {
   if(!param){return}
   return param.constructor === Object ?  true : false;
}

Calling - Object.isObject()

Object.isObject({})                       // returns true
Object.isObject([])                       // returns false
Object.isObject("I am string")            // returns false
Rigi answered 12/8, 2021 at 10:13 Comment(0)
K
0

This answer will evaluate inner arrays values:

function cleanObj(obj) {
  for (var propName in obj) {
    if (obj[propName] === null || obj[propName] === undefined) {
      delete obj[propName];
    } else if (Array.isArray(obj[propName])) {
      obj[propName] = obj[propName].map((item) => {
        return cleanObj(item);
      });
    } else if (typeof obj[propName] === 'object') {
      obj[propName] = cleanObj(obj[propName]);
    }
  }
  return obj;
}
Knotting answered 17/5, 2023 at 20:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.