How can I differentiate between an object literal other Javascript objects?

20,950

Solution 1

How can I tell the difference between an object literal and any other Javascript object (e.g. a DOM node, a Date object, etc.)?

The short answer is you can't.

An object literal is something like:

var objLiteral = {foo: 'foo', bar: 'bar'};

whereas the same object created using the Object constructor might be:

var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';

I don't think there is any reliable way to tell the difference between how the two objects were created.

Why is it important?

A general feature testing strategy is to test the properties of the objects passed to a function to determine if they support the methods that are to be called. That way you don't really care how an object is created.

You can employ "duck typing", but only to a limited extent. You can't guarantee that just because an object has, for example, a getFullYear() method that it is a Date object. Similarly, just because it has a nodeType property doesn't mean it's a DOM object.

For example, the jQuery isPlainObject function thinks that if an object has a nodeType property, it's a DOM node, and if it has a setInterval property it's a Window object. That sort of duck typing is extremely simplistic and will fail in some cases.

You may also note that jQuery depends on properties being returned in a specific order - another dangerous assumption that is not supported by any standard (though some supporters are trying to change the standard to suit their assumed behaviour).

Edit 22-Apr-2014: in version 1.10 jQuery includes a support.ownLast property based on testing a single property (apparently this is for IE9 support) to see if inherited properties are enumerated first or last. This continues to ignore the fact that an object's properties can be returned in any order, regardless of whether they are inherited or own, and may be jumbled.

Probably the simplest test for "plain" objects is:

function isPlainObj(o) {
  return typeof o == 'object' && o.constructor == Object;
}

Which will always be true for objects created using object literals or the Object constructor, but may well give spurious results for objects created other ways and may (probably will) fail across frames. You could add an instanceof test too, but I can't see that it does anything that the constructor test doesn't.

If you are passing ActiveX objects, best to wrap it in try..catch as they can return all sorts of weird results, even throw errors.

Edit 13-Oct-2015

Of course there are some traps:

isPlainObject( {constructor: 'foo'} ); // false, should be true

// In global scope
var constructor = Object;
isPlainObject( this );        // true, should be false

Messing with the constructor property will cause issues. There are other traps too, such as objects created by constructors other than Object.

Since ES5 is now pretty much ubiquitous, there is Object.getPrototypeOf to check the [[Prototype]] of an object. If it's the buit–in Object.prototype, then the object is a plain object. However, some developers wish to create truly "empty" objects that have no inherited properties. This can be done using:

var emptyObj = Object.create(null);

In this case, the [[Prototype]] property is null. So simply checking if the internal prototype is Object.prototype isn't sufficient.

There is also the reasonably widely used:

Object.prototype.toString.call(valueToTest)

that was specified as returning a string based on the internal [[Class]] property, which for Objects is [object Object]. However, that has changed in ECMAScript 2015 so that tests are performed for other types of object and the default is [object Object], so the object may not be a "plain object", just one that isn't recognised as something else. The specification therefore notes that:

"[testing using toString] does not provide a reliable type testing mechanism for other kinds of built-in or program defined objects."

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.tostring

So an updated function that allows for pre–ES5 hosts, objects with a [[Prototype]] of null and other object types that don't have getPrototypeOf (such as null, thanks Chris Nielsen) is below.

Note that there is no way to polyfill getPrototypeOf, so may not be useful if support for older browsers is required (e.g. IE 8 and lower, according to MDN).

/*  Function to test if an object is a plain object, i.e. is constructed
**  by the built-in Object constructor and inherits directly from Object.prototype
**  or null. Some built-in objects pass the test, e.g. Math which is a plain object
**  and some host or exotic objects may pass also.
**
**  @param {} obj - value to test
**  @returns {Boolean} true if passes tests, false otherwise
*/
function isPlainObject(obj) {

  // Basic check for Type object that's not null
  if (typeof obj == 'object' && obj !== null) {

    // If Object.getPrototypeOf supported, use it
    if (typeof Object.getPrototypeOf == 'function') {
      var proto = Object.getPrototypeOf(obj);
      return proto === Object.prototype || proto === null;
    }
    
    // Otherwise, use internal class
    // This should be reliable as if getPrototypeOf not supported, is pre-ES5
    return Object.prototype.toString.call(obj) == '[object Object]';
  }
  
  // Not an object
  return false;
}


// Tests
var data = {
  'Host object': document.createElement('div'),
  'null'       : null,
  'new Object' : {},
  'Object.create(null)' : Object.create(null),
  'Instance of other object' : (function() {function Foo(){};return new Foo()}()),
  'Number primitive ' : 5,
  'String primitive ' : 'P',
  'Number Object' : new Number(6),
  'Built-in Math' : Math
};

Object.keys(data).forEach(function(item) {
  document.write(item + ': ' + isPlainObject(data[item]) + '<br>');
});

Solution 2

Similar to @RobG example:

function isPlainObject(obj) {
    return  typeof obj === 'object' // separate from primitives
        && obj !== null         // is obvious
        && obj.constructor === Object // separate instances (Array, DOM, ...)
        && Object.prototype.toString.call(obj) === '[object Object]'; // separate build-in like Math
}

TEST:

function isPlainObject(obj) {
	return	typeof obj === 'object'
		&& obj !== null
		&& obj.constructor === Object
		&& Object.prototype.toString.call(obj) === '[object Object]';
}

var data = {
  '{}': {},
  'DOM element': document.createElement('div'),
  'null'       : null,
  'Object.create(null)' : Object.create(null),
  'Instance of other object' : new (function Foo(){})(),
  'Number primitive ' : 5,
  'String primitive ' : 'P',
  'Number Object' : new Number(6),
  'Built-in Math' : Math
};

Object.keys(data).forEach(function(item) {
  document.write(item + ':<strong>' + isPlainObject(data[item]) + '</strong><br>');
});

Solution 3

Since all DOM Nodes inherit from the Node interface you could try the following:

if(typeof x === 'string') {
    //string
} else if(x instanceof Node) {
    //DOM Node
} else {
    //everything else
}

But I'm not sure if this works in older versions of Internet Explorer

Solution 4

If you don't mind using a package i would recommend using lodash for this:

https://lodash.com/docs/4.17.15#isPlainObject

Solution 5

Maybe something like this?

var isPlainObject = function(value){
    if(value && value.toString && value.toString() === '[object Object]')
        return true;

    return false;
};

Or this other approach:

var isObject = function(value){
    var json;

    try {
        json = JSON.stringify(value);
    } catch(e){

    }

    if(!json || json.charAt(0) !== '{' || json.charAt(json.length - 1) !== '}')
        return false;

    return true;
};
Share:
20,950
Will McCutchen
Author by

Will McCutchen

Updated on July 07, 2020

Comments

  • Will McCutchen
    Will McCutchen almost 4 years

    Update: I'm rephrasing this question, because the important point to me is identifying the object literal:

    How can I tell the difference between an object literal and any other Javascript object (e.g. a DOM node, a Date object, etc.)? How can I write this function:

    function f(x) {
        if (typeof x === 'object literal')
            console.log('Object literal!');
        else
            console.log('Something else!');
    }
    

    So that it only prints Object literal! as a result of the first call below:

    f({name: 'Tom'});
    f(function() {});
    f(new String('howdy'));
    f('hello');
    f(document);
    

    Original Question

    I'm writing a Javascript function that is designed to accept an object literal, a string, or a DOM node as its argument. It needs to handle each argument slightly differently, but at the moment I can't figure out how to differentiate between a DOM node and a plain old object literal.

    Here is a greatly simplified version of my function, along with a test for each kind of argument I need to handle:

    function f(x) {
        if (typeof x == 'string')
            console.log('Got a string!');
        else if (typeof x == 'object')
            console.log('Got an object literal!');
        else
            console.log('Got a DOM node!');
    }
    
    f('hello');
    f({name: 'Tom'});
    f(document);
    

    This code will log the same message for the second two calls. I can't figure out what to include in the else if clause. I've tried other variations like x instanceof Object that have the same effect.

    I understand that this might be bad API/code design on my part. Even if it is, I'd still like to know how to do this.

  • Will McCutchen
    Will McCutchen about 13 years
    I'm sorry, but I updated my question after you answered it because I realized that I needed to emphasize the fact that accurately identifying object literals, if possible, is my actual goal.
  • RobG
    RobG about 13 years
    You don't know that. While it may make sense to implement DOM objects using prototype inheritance that mimicks the scheme suggested by the W3C DOM specs, there is no reason to expect all browsers to to do it. In fact, browsers don't have to implement any kind of inheritance for host objects.
  • Will McCutchen
    Will McCutchen about 13 years
    Thanks for the thorough answer (which mostly confirmed my assumptions). I think your isPlainObj function is accurate enough for my purposes. Much obliged!
  • standardModel
    standardModel about 13 years
    Well, I've tested it in Chrome (so it will probably work in Safari too), in Firefox and in IE9, so it seems like at least all modern browsers work this way.
  • tomekwi
    tomekwi about 9 years
    @standardModel @RobG I’ve just come across an in-depth article by kangax which warns against instanceof Node. I don’t know of any reliable alternative though.
  • RobG
    RobG about 9 years
    @tomekwi—that article is from 2010, the principles expressed were well known previously but not documented as eloquently until then (see Richard Cornford's response here). They're still valid today. The point being made is that while the DOM specifications infer inheritance, they do not specify prototype inheritance (though there is a strong leaning toward ECMAScript in newer specifications that once were much more language neutral). And instanceof will likely fail across frames, it does for native objects.
  • RobG
    RobG about 9 years
    @standardModel—here's the original of the Richard Cornford link. It was on comp.lang.javascript, which has been subsumed by Google Groups: Client-side DOM: are 'window' and 'document' real objects?. Note Richard's warning about extending the prototype of host objects pre–dates kangax's article by about 7 years. Kangax was involved in prototype.js, he learned the hard way. ;-)
  • Chris Nielsen
    Chris Nielsen over 8 years
    Be aware that typeof null === "object" and Object.getPrototypeOf(null) will throw an error. If you wish for null input to return false instead of throwing an error, your isPlainObject function should include a check for null.
  • RobG
    RobG over 8 years
    @ChrisNielsen—thanks for bringing me back to this. Given that since ES5 it's been possible to create objects with null as their [[Prototype]] using Object.create(null), the above is now very much out of date and will be updated as soon as I have time.
  • King Friday
    King Friday over 6 years
    "duck typing", that's perfect. useful answer.
  • dy_
    dy_ about 5 years
    isPlainObject({toString: () => 123}) //false