Differentiating between arrays and "hashes" in Javascript - javascript

In order to make the syntax for one of my functions nicer, I need to be able to tell whether a specific parameter is an array or "hash" (which I know are just objects).
Typeof doesn't work, because they both return the same thing
typeof {foo:"bar"} // Object
typeof ["foo","bar"] // Object
So how would I differentiate between the two?
I know this works, but I'm hoping there's a nicer way
({foo:"bar"}).constructor // Object()
(["foo","bar"]).constructor // [ undefined ]
EDIT
Ah, it seems [ undefined ] in firebug is the same thing as Array. Kind of weird.

You could check the length property as SLaks suggested, but as soon as you pass it a function object you'll be surprised, because it in fact has a length property. Also if the object has a length property defined, you'll get wrong result again.
Your best bet is probably:
function isArray(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
}
jQuery uses it, and a "couple of" other people... :)
It is more fail proof than the instanceof way. The method is also suggested by the following article:
'instanceof' considered harmful (or how to write a robust 'isArray') (#kagax)
Another thing to add that this function is almost identical to the Array.isArray function in ES 5 spec:
15.4.3.2 Array.isArray ( arg )
If Type(arg) is not Object, return
false.
If the value of the [[Class]]
internal property of arg is "Array",
then return true.
Return false.

something instanceof Array works fine within a single document, but will fail if you start passing arrays between different windows, because the Array from one window is a different object from Array on another. If you have no intention of doing cross-window-scripting (and in general, it's worth avoiding) I would recommend sticking with this.
If you need cross-window support, things are a bit more complex. In the future the story is simple, as ECMAScript Fifth Edition defines a function to do exactly this:
Array.isArray([1]); // -> true
You should use this functionality where available as it's the only reliable and standards-endorsed way. However, many of today's browsers don't support it yet.
Where it isn't available you have to rely on Object#toString serialisation, which is ugly and slightly dodgy. Although it will in general work reliably with today's browsers, there are imaginable cases where it might not (primarily to do with host objects).
You can hack this fallback method into Array on browsers that don't support it, and then use Array.isArray at all times:
if (!('isArray' in Array)) {
Array.isArray= function(o) {
return Object.prototype.toString.call(o)==='[object Array]';
};
}
As for constructor, never use it. It's not available everywhere and it doesn't do what you think. Using it is almost always a mistake.

I think the most elegant way is to simply use the instanceof operator:
if (myVar instanceof Array)
doSomething();
examples:
[] instanceof Array // true
{} instanceof Array // false
{length:100} instanceof Array // false
null instanceof Array // false
Edit:
Be aware that it will fail when testing an object from another iFrame (see answers by #galambalazs and #bobince)

Check for the length property:
"length" in {foo:"bar"} //false
"length" in ["foo","bar"] //true

This typeof function is used to correct the array/object clash for the typeof operator, and the null/object clash. It does not however work across frames or across windows. See the source for a more advanced version that works across these.
function typeOf(value) {
var s = typeof value;
if (s === 'object') {
if (value) {
if (value instanceof Array) {
s = 'array';
}
} else {
s = 'null';
}
}
return s;
}
Source: Douglas Crockford's Javascript site

Related

Why is there not a built-in method in JavaScript to check if an object is a plain object?

Given the developments of JavaScript since the languages' inception, why is there not a built in method that checks if an object is a plain object?
Or does the method in fact exist?
You can check the type and the instance of an object this way:
var a = new Date();
console.log(typeof a);
console.log(a instanceof Date);
var b = "Hello";
console.log(typeof b);
console.log(b instanceof Date);
Updated according to the comments from the OP:
let arr = [1, 2, true, 4, {
"abc": 123
},
6, 7, {
"def": 456
},
9, [10], {}, "[object Object]"
];
arr.forEach(function(v) {
if (typeof v == "object" && !(v instanceof Array) && v != null)
console.log("Object Found");
else
; // console.log("Na");
});
The above code snippets outputs thrice Object Found.
There doesn't exist any explicit direct way to check if a value is an object, i.e. belongs to Object type, but there are some foolproof ways to do it. I wrote a list in another answer, the most succinct seems
function isObject(value) {
return Object(value) === value;
}
A feature like this has been requested multiple times on esdiscuss. For example,
What is an Object Type(O)?
Juriy Zaytsev "kangax" wonders about a proper way to check if a value is an object.
typeof null
Brendan Eich: "I think we should consider Object.isObject"
Jorge: "Why not .isPrimitive()?"
ES6 doesn't need opt-in
Brendan Eich: "We want sane isObject and isNull predicates"
Axel Rauschmayer: "predicates such as isObject() and isPrimitive()"
In fact, Object.isObject was proposed as strawman, and it appeared in an ES6 early draft.
TC39 bashing: Discussion about Object.isObject in the ES6 draft.
How primitive are Symbols? Bignums? etc: discusses x === Object(x)
Object.isObject strawman was eventually rejected and removed from ES6 draft.
More recently,
ES8 Proposal: Optional Static Typing (Brandon Andrews): Includes Object.isObject
Now there is the is{Type} Methods stage 0 proposal which includes Object.isObject among lots of various other checks.
So there is still hope and eventually we may have something like this.
The above is for testing objects in general. If you don't want that you should define what "plain object" means for you.
For example, you can test the constructor property. But any object can customize it.
You can use Object.prototype.toString to get the legacy ES5 [[Class]]. But any object can customize that via Symbol.toStringTag.
You can check the value returned by [[GetPrototypeOf]]. But even exotic objects might allow their prototype to be changed to whatever arbitrary object or null. And Proxy objects even have full control over that internal method.
So most probably you won't be able to rely on these tests. And adding something to the standard may be hard because different people may want different things.
What I would like is some way to check if an object is an ordinary one. That is, it has the default behaviour for the essential internal methods that must be supported by all objects.
Once you know that an object is ordinary, you can rely on things like [[GetPrototypeOf]] to customize the test to your tastes.
Relying on [object Object] string representation is inaccurate. This behaviour may be changed for any objects with:
let o = { toString: () => '...' };
('' + o) !== '[object Object]'
var a = [];
a.toString = () => '[object Object]';
('' + a) === '[object Object]';
The most solid way to check if a value is a plain object is
let o = {}
Object.getPrototypeOf(o) === Object.prototype
And considering that constructor property wasn't tampered, the most straightforward way to check if a value is a plain object is
let o = {}
o.constructor === Object
This covers all POJOs constructed from Object and doesn't cover Object.create(null, { ... }) or any child classes (including built-ins like RegExp or Array):
Object.create(null).constructor !== Object
[].constructor !== Object
(new class {}).constructor !== Object
One of the possible reasons why there is no dedicated method to check for object plainness is because a restriction to use only {} objects is not practical. This makes very little sense in the context of JS. This prevents the use of any class instances or relatively 'plain' objects (Object.create({}, ...)).
This would require the hack in order for desired non-plain objects to pass the check:
Object.assign({}, (new class {})).constructor === Object
In most cases of object checking 'everything which is not forbidden is allowed' principle pays off (with extra caution regarding infamous null inconsistency).
Applying the above to this case, a safe and concise condition to filter non-array objects is
o && typeof o === 'object' && !Array.isArray(o)
And a condition to filter objects that are not built-ins (functions, Array, RegExp, etc) is
o && (o.constructor === Object || !/\[native code\]/.test(o.constructor))
Just for the sake of further documenting different ways:
One way I can think of:
JSON.stringify(testValue)[0] === '{';
Keep in mind that objects with circular references cannot be stringified. However, if you are certain that all testValues cannot have circular references, you have yourself a way to check against Null, Arrays, and any primitive value to ensure that you have an Object.
I suggest that if you plan on using this throughout your code though, that you define a helper function that actually implements it, in case you find that this does not work as you expect and end up having to change the way you check it.
Every thing JavaScript is an Object , so there is no need to have an isObject api

Why do lodash's .isObject, .isPlainObject behave differently than "typeof x === 'object'"?

Consider the following:
var o1 = {}
var O = function () {
return this
}
var o2 = new O()
var o3 = function() {}
var o4 = [o1, o1]
var output = [
[_.isObject(o1), _.isObject(o2), _.isObject(o3), _.isObject(o4)],
[_.isPlainObject(o1), _.isPlainObject(o2), _.isPlainObject(o3), _.isPlainObject(o4)],
[typeof o1 === 'object', typeof o2 === 'object', typeof o3 === 'object', typeof o4 === 'object'],
[o1 instanceof Array, o2 instanceof Array, o3 instanceof Array, o4 instanceof Array]
]
/* outputs:
[
[true,true,true,true],
[true,false,false,false],
[true,true,false,true],
[false,false,false,true]
]
*/
Clearly we can see that there is a disconnect between .isObject():
Which checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), and new String(' ')
.isPlainObject():
Which if value is a plain object, that is, an object created by the Object constructor or one with a Prototype of null
And our good ol' trusty friend typeof x === 'object'.
I have three questions:
Was there a conscious design decision to make .isObject and .isPlainObject behave differently than the native .js type checking?
If my first question is true, what was the design decision and what are the benefits of doing it this way?
Is there any native lodash (or underscore.js) is* function that behaves exactly the same as typeof x === 'object'?
Obviously I can just continue to use typeof, but syntactically it's a bit weird to use one or the other in some places, for example the usage of .isObject will return false positives when checking for typeof x === 'object' && typeof x !== 'function'. I don't really see any benefit of .isObject returning true for functions when .isFunction already exists.
typeof has nothing to do with whether something is an object. Functions, strings, and {} have different typeof and they are all objects. Functions are of course first-class objects, just like strings are first-class objects, therefore isObject must return true for strings and objects.
For the record the documentation covers this:
Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
Wow! that really is a lot to test for without having a handy isObject method. To be fair most return typeof as object, but the point of higher level methods, especially in libraries like lodash, is so the programmer can forget about that nonsense.
If you care about the typeof an argument, then use typeof. If you care about objects that are not functions, you have a couple options: you can use typeof and check strings specially, or you can use isObject && !isFunction. I would prefer the latter. The latter does happen to say exactly what you are trying to convey so it really is the correct thing to code. If you think when you say "object" that you implicitly don't mean functions, then you do not think functions are first-class objects, or rather you would like your code to more closely resemble a language in which they're not. But then you can't blame lodash for being a library that extensively uses the fact that functions are first-class objects to make a language in which functions are first-class objects more expressive.
I believe that was the bulk of your question. I believe the use case for isPlainObject is to answer the question "is this just data?" or "is this code?" so objects created as pseudo-classes (new of something) don't count.
Sometimes code speaks louder than words. Here's the source from lodash:
function isObject(value) {
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
function isPlainObject(value) {
var Ctor;
if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||
(!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
return false;
}
var result;
if (lodash.support.ownLast) {
baseForIn(value, function(subValue, key, object) {
result = hasOwnProperty.call(object, key);
return false;
});
return result !== false;
}
baseForIn(value, function(subValue, key) {
result = key;
});
return result === undefined || hasOwnProperty.call(value, result);
}
According to the lodash docs:
isObject
Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
isPlainObject
Checks if value is a plain object, that is, an object created by the Object constructor or one with a [[Prototype]] of null.
What is an object?
Perhaps the existence of an isObject function is inherently a bit confusing when in JavaScript so many things act like objects. The key idea is that some values in JavaScript are considered primitives and others considered full-blown objects. Strings, numbers, and booleans are treated differently at an internal level than objects created with the object literal or constructor functions (this doesn't end up have much practical significance because primitives will automatically cast themselves to objects when necessary).
The fact that typeof null === 'object' probably originates from the fact that objects are reference types and null is often returned from functions in lieu of an object. (A quirk which likely hearkens back to the concept of pointers and the NULLPTR in C and C++. The type void * has many values but the NULLPTR is still considered a valid value for the pointer type.)
The lodash docs describes Lodash as "a JavaScript utility library delivering consistency, modularity, performance, & extras." isObject and isPlainObject would be an extra. They are utility functions. I'm sure they were aware that they are different then typeof which is why they may be useful to some people. They probably thought the performance, consistency, and syntax of the native typeof didn't warrant the making of .typeOf that does the same thing.
As mentioned above, the benefits are that it functions a little differently than typeof which may be handy to some people. Also lodash focuses on better performance as well, although I'm not sure if there performance of such a simple function could be significantly improved. It is said to improve the performance of loops though. You can test the performance difference using JSPerf and let us know! As far as why they decided to have .isObject return true for functions is because functions in JS are objects. It is kind of deceiving when native typeof returns function and not object. It is further convoluted by the fact that typeof [] doesnt return array and instead returns object so why should it return function and not object on a function.
Neither lodash or underscore appear to have a function that is the same as typeof. Underscore has _.isObject which is the same as the lodash .isObject. You could use lodash's .isFunction and .isObject to create your own typeof function that returns the same thing native typeof would.

What are the edge cases when using Object.prototype.toString?

So far I have relied on Object.prototype.toString.call(x) to distinguish between the different native object types in Javascript, arrays in particular.
If you subclass arrays, you get some strange behavior:
function Ctor() {}
Ctor.prototype = Object.create(Array.prototype);
var x = new Ctor();
x.push(1);
Object.prototype.toString.call(x); // [object Object]
Probably this is documented in the ES5 specs (and no longer an issue in ES6), but I consider it a quirk of the current version of the language. I adapted my corresponding functions as follows:
function objTypeOf(deep, type) {
return function _objTypeOf(x) {
do {
if (Object.prototype.toString.call(x).slice(8, -1).toLowerCase() === type) return true;
x = Object.getPrototypeOf(x);
} while(deep && x !== null);
return false;
};
}
var arr = objTypeOf(false, "array"),
arrP = objTypeOf(true, "array"); // array prototype
console.log(arr(x)); // false
console.log(arrP(x)); // true
objTypeOf checks the current object and the entire prototype chain until there is a type match. It accepts an object even if merely one of the prototypes matches the expected type. objTypeOf is not based on prototype identities, but on strings (lacking identity).
I wonder now if there are other edge cases when using Object.prototype.toString, that need special treatment?
Well your problem is not with Object.prototype.toString, but that you tried to subclass arrays. It just doesn't work, and toString correctly tells you that you failed to create an array. It's merely an object that has Array.prototype in its prototype chain (if that was what you cared for, use instanceof Array).
Regardless, to answer your title question:
What are the edge cases when using Object.prototype.toString?
Host objects. Everything that is not a native JS object, despite looking like one, might return any [[Class]] value that you didn't expect. There are even known cases where callable objects do not report Function.

Distinguishing between defined and generic JSON objects in JavaScript

A project I've been working on recently gave me the need to create a function which can return a complete copy of a JSON object, recursively copying any internal objects. After a couple of failed attempts, I came up with this:
function copyObj(obj) {
var copy;
if (obj instanceof Array) {
copy = [];
for (var i in obj) {
copy.push(copyObj(obj[i]));
}
}
else if (obj instanceof Object) {
copy = {};
for (var prop in obj) {
copy[prop] = copyObj(obj[prop]);
}
}
else {
copy = obj;
}
return copy;
}
The function works perfectly for my purposes, which are to copy objects that will only ever contain primitive types, arrays, and nested generic JSON objects. For example, it will return a flawless copy of this: { prop1:0, prop2:'test', prop3:[1, 2, 3], prop4:{ subprop1:['a', 'b', 'c'], subprop2:false } }.
There's one thing about this function that's nagging at me, though - its inability to handle any other types of objects (e.g. the RegExp object). I'd like to improve on it by adding the capability to handle them, but at the same time I'd really rather not just have a huge wall of else if (obj instanceof [insert object type here]). As such, my question is this: Is there a simple way in JavaScript of differentiating between a generic object (i.e. one declared as var obj = { }) and one with a proper prototype/constructor? And if so, is there also a simple generalized way of copying such objects? My expectation for the second part of the question is no, and that I'd still need special handling to call constructors, but I'd still like to know with certainty either way.
P.S. In case anyone was curious about the context, the project requires me to manipulate a large list of items on a server, but in different ways for different connected clients. The easiest way I could think of to handle that was to create one master list and then have the server clone a fresh copy to manipulate without altering the master list for every new client that connects, hence the need for copyObj().
Edit: I probably should have mentioned this in the original question - this is running with node.js as a server, not in a browser, so browser cross-compatibility isn't an issue.
Edit 2: In the interest of not cluttering the comments too much, I'll mention it here: I tried a quick benchmarking of my copyObj() function against the JSON.parse(JSON.stringify(obj)) exploit using the example object above. My version seems to run in about 75% of the time that the JSON method takes (1 million copies took ~3.2 seconds for mine and ~4.4 seconds for JSON). So that makes me feel better about having taken the time to write my own.
Edit 3: Working off of the list of object types in Vitum.us's answer, I threw together an updated version of my copyObj() function. I haven't tested it extensively, and the performance is about 2x worse than the old version, but I think it should actually work for all built-in types (assuming that list was complete).
function copyObjNew(obj) {
var copy;
if (obj.constructor === Object) {
// Generic objects
copy = {};
for (var prop in obj) {
copy[prop] = copyObjNew(obj[prop]);
}
}
else if (obj.constructor === Array) {
// Arrays
copy = [];
for (var i in obj) {
copy.push(copyObjNew(obj[i]));
}
}
else if (obj.constructor === Number || obj.constructor === String || obj.constructor === Boolean) {
// Primitives
copy = obj;
}
else {
// Any other type of object
copy = new obj.constructor(obj);
}
return copy;
}
I'm using the .constructor property now, as Mike suggested, and it seems to be doing the trick. I've tested it so far with RegExp and Date objects, and they both seem to copy correctly. Do any of you see anything blatantly (or subtly) incorrect about this?
One way of detecting plain JS objects (created with {} or new Object) is to use the jQuery method jQuery.isPlainObject.
However, the documentation says that "Host objects have a number of inconsistencies which are difficult to robustly feature detect cross-platform. As a result of this, $.isPlainObject() may evaluate inconsistently across browsers in certain instances." Whether this works reliably with node.js should be tested.
Edit: in response to your comment: you can use jQuery with node.js, see this question: Can I use jQuery with Node.js?
Otherwise it is also possible to just copy the jQuery implementation of the method to your project, as the MIT license (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) seems to permit this. jQuery implementation:
isPlainObject: function( obj ) {
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
// - window
if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
// Support: Firefox <20
// The try/catch suppresses exceptions thrown when attempting to access
// the "constructor" property of certain host objects, ie. |window.location|
// https://bugzilla.mozilla.org/show_bug.cgi?id=814622
try {
if ( obj.constructor &&
!core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
return false;
}
} catch ( e ) {
return false;
}
// If the function hasn't returned already, we're confident that
// |obj| is a plain object, created by {} or constructed with new Object
return true;
}
You can use this to detect if a object is a regular expression
Object.prototype.toString.call( regexpObject ) == "[object RegExp]"
This is the way mentioned in the specification for getting the class of object.
From ECMAScript 5, Section 8.6.2 Object Internal Properties and Methods:
The value of the [[Class]] internal property is defined by this specification for every kind of built-in object. The value of the [[Class]] internal property of a host object may be any String value except one of "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String". The value of a [[Class]] internal property is used internally to distinguish different kinds of objects. Note that this specification does not provide any means for a program to access that value except through Object.prototype.toString (see 15.2.4.2).
A RegExp is a class of object defined in the spec at Section 15.10 RegExp(RegularExpression)Objects:
A RegExp object contains a regular expression and the associated flags.
Then, you can copy a RegExp object using new RegExp()
var oldObject = /[a-z]+/;
var newObject = new RegExp(oldObject);

JavaScript: detect if argument is array instead of object (Node.JS)

How should I detect if the argument is an array because typeof [] returns 'object' and I want to distinguish between arrays and objects.
It is possible that object will look like {"0":"string","1":"string","length":"2"} but I don't want it to come out as an array if it is in fact an object looking like an array.
JSON.parse and JSON.stringify are able to make this distinction. How can I do it?
I am using Node.JS which is based on V8 the same as Chrome.
Array.isArray
native V8 function. It's fast, it's always correct. This is part of ES5.
arr instanceof Array
Checks whether the object was made with the array constructor.
_.isArray // underscore method.
A method from underscore. Here is a snippet taken from the their source
var toString = Object.prototype.toString,
nativeIsArray = Array.isArray;
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
This method takes an object and calls the Object.prototype.toString method on it. This will always return [object Array] for arrays.
In my personal experience I find asking the toString method is the most effective but it's not as short or readable as instanceof Array nor is it as fast as Array.isArray but that's ES5 code and I tend to avoid using it for portability.
I would personally recommend you try using underscore, which is a library with common utility methods in it. It has a lot of useful functions that DRY up your code.
Try this code:
Array.isArray(argument)
How about:
your_object instanceof Array
In V8 in Chrome I get
[] instanceof Array
> true
({}) instanceof Array
> false
({"0":"string","1":"string","length":"2"}) instanceof Array
> false
I looks like this question has several good answers, but for completeness I would add another option, which have not been suggested earlier.
In order to check if something is an array, you can use Node.js util native module and its isArray() function.
Example:
var util = require('util');
util.isArray([]); // true
util.isArray(new Array); // true
util.isArray({"0":"string","1":"string","length":"2"}); // false
With that method you do not have to worry about JS standards implemented by V8 as it will always show the right answer.
Try this way:
console.log(Object.prototype.toString.call(arg).replace(/^[object (.+)]$/, '$1').toLowerCase())

Categories

Resources