Test for existence of nested JavaScript object key

Test for existence of nested JavaScript object key

If I have a reference to an object:
var test = {};

that will potentially (but not immediately) have nested objects, something like:
{level1: {level2: {level3: “level3”}}};

What is the best way to check for the existence of property in deeply nested objects?
alert(test.level1); yields undefined, but alert(test.level1.level2.level3); fails.
I’m currently doing something like this:
if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
alert(test.level1.level2.level3);
}

but I was wondering if there’s a better way.

Solutions/Answers:

Solution 1:

You have to do it step by step if you don’t want a TypeError, because if one of the members is null or undefined, and you try to access a member an exception will be thrown.

You can either simply catch the exception, or make a function to test the existence of multiple levels, something like this:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

UPDATE 2019-05-16:

Here is a shorter version, using ES6 features and recursion (it’s also in proper tail call form):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

Solution 2:

Here is a pattern I picked up from Oliver Steele:

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

In fact that whole article is a discussion of how you can do this in javascript. He settles on using the above syntax (which isn’t that hard to read once you get used to it) as an idiom.

Solution 3:

Update

Looks like lodash has added _.get for all your nested property getting needs.

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


Previous answer

lodash users may enjoy lodash.contrib which has a couple methods that mitigate this problem.

getPath

Signature: _.getPath(obj:Object, ks:String|Array)

Gets the value at any depth in a nested object based on the path described by
the keys given. Keys may be given as an array or as a dot-separated string.
Returns undefined if the path cannot be reached.

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined

Solution 4:

I have done performance tests (thank you cdMinix for adding lodash) on some of the suggestions proposed to this question with the results listed below.

Disclaimer #1 Turning strings into references is unnecessary meta-programming and probably best avoided. Don’t lose track of your references to begin with. Read more from this answer to a similar question.

Disclaimer #2 We are talking about millions of operations per millisecond here. It is very unlikely any of these would make much difference in most use cases. Choose whichever makes the most sense knowing the limitations of each. For me I would go with something like reduce out of convenience.

Object Wrap (by Oliver Steele) – 34 % – fastest

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

Original solution (suggested in question) – 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

checkNested – 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist – 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

validChain – 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys – 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

nestedPropertyExists – 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_.get – 72%

deeptest – 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

sad clowns – 100% – slowest

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);

Solution 5:

You can read an object property at any depth, if you handle the name like a string: 't.level1.level2.level3'.

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

It returns undefined if any of the segments is undefined.

Solution 6:

var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

If you are coding in ES6 environment (or using 6to5) then you can take advantage of the arrow function syntax:

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

Regarding the performance, there is no performance penalty for using try..catch block if the property is set. There is a performance impact if the property is unset.

Consider simply using _.has:

var object = { 'a': { 'b': { 'c': 3 } } };

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true