20

Let's say we have this JavaScript object:

var object = {
   innerObject:{
       deepObject:{
           value:'Here am I'
       }
   }
};

How can we check if value property exists?

I can see only two ways:

First one:

if(object && object.innerObject && object.innerObject.deepObject && object.innerObject.deepObject.value) {
    console.log('We found it!');
}

Second one:

if(object.hasOwnProperty('innerObject') && object.innerObject.hasOwnProperty('deepObject') && object.innerObject.deepObject.hasOwnProperty('value')) {
    console.log('We found it too!');
}

But is there a way to do a deep check? Let's say, something like:

object['innerObject.deepObject.value']

or

object.hasOwnProperty('innerObject.deepObject.value')
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BadVolt
  • 617
  • 1
  • 7
  • 19
  • Sure, using any of a number libraries that support that. – Dave Newton Oct 30 '15 at 20:54
  • You can easily write a function that takes a string like that, splits it into an array of property names, and goes into a loop checking whether each property exists. – Barmar Oct 30 '15 at 20:56

8 Answers8

26

There isn't a built-in way for this kind of check, but you can implement it easily. Create a function, pass a string representing the property path, split the path by ., and iterate over this path:

Object.prototype.hasOwnNestedProperty = function(propertyPath) {
  if (!propertyPath)
    return false;

  var properties = propertyPath.split('.');
  var obj = this;

  for (var i = 0; i < properties.length; i++) {
    var prop = properties[i];

    if (!obj || !obj.hasOwnProperty(prop)) {
      return false;
    } else {
      obj = obj[prop];
    }
  }

  return true;
};

// Usage:
var obj = {
  innerObject: {
    deepObject: {
      value: 'Here am I'
    }
  }
}

console.log(obj.hasOwnNestedProperty('innerObject.deepObject.value'));
bli07
  • 673
  • 1
  • 5
  • 16
Viktor Bahtev
  • 4,858
  • 2
  • 31
  • 40
17

You could make a recursive method to do this.

The method would iterate (recursively) on all 'object' properties of the object you pass in and return true as soon as it finds one that contains the property you pass in. If no object contains such property, it returns false.

var obj = {
  innerObject: {
    deepObject: {
      value: 'Here am I'
    }
  }
};

function hasOwnDeepProperty(obj, prop) {
  if (typeof obj === 'object' && obj !== null) { // only performs property checks on objects (taking care of the corner case for null as well)
    if (obj.hasOwnProperty(prop)) {              // if this object already contains the property, we are done
      return true;
    }
    for (var p in obj) {                         // otherwise iterate on all the properties of this object.
      if (obj.hasOwnProperty(p) &&               // and as soon as you find the property you are looking for, return true
          hasOwnDeepProperty(obj[p], prop)) { 
        return true;
      }
    }
  }
  return false;                                  
}

console.log(hasOwnDeepProperty(obj, 'value'));   // true
console.log(hasOwnDeepProperty(obj, 'another')); // false
nem035
  • 34,790
  • 6
  • 87
  • 99
  • I'm afraid this function will look for property in any inner object, not specific. – BadVolt Oct 30 '15 at 21:29
  • Oh, I guess I misunderstood you. Well, [**Viktor Bahtev's answer**](http://stackoverflow.com/a/33445095/3928341) is the way to go then. – nem035 Oct 30 '15 at 21:30
  • 3
    @nem nice broadly usable solution nonetheless! – wintvelt Oct 30 '15 at 21:32
  • Yeah, may be it was not the best example. I have to iterate over some objects with errors and each of this objects has same property 'message'. Your example is great, but not for my case :) – BadVolt Oct 30 '15 at 21:33
  • So long as you don't have any circular references. – Daniel Flint Oct 30 '15 at 21:34
  • **BadVolt**, **wintvelt** thanks! I'll still leave it up here in case somebody needs it. **DanielFlint**, good eye, you're right. This code doesn't take care of circular references but this can be solved with memoization of previously visited paths but I guess it's kind of pointless to do now that the question is resolved :) – nem035 Oct 30 '15 at 21:38
  • @nem035, thumbs up for this solution. It was just what I needed :-) – enf0rcer Oct 23 '17 at 15:04
3

Alternative recursive function:

Loops over all object keys. For any key it checks if it is an object, and if so, calls itself recursively.

Otherwise, it returns an array with true, false, false for any key with the name propName.

The .reduce then rolls up the array through an or statement.

function deepCheck(obj,propName) {
  if obj.hasOwnProperty(propName) {             // Performance improvement (thanks to @nem's solution)
    return true;
  }
  return Object.keys(obj)                       // Turns keys of object into array of strings
    .map(prop => {                              // Loop over the array
      if (typeof obj[prop] == 'object') {       // If property is object,
        return deepCheck(obj[prop],propName);   // call recursively
      } else {
        return (prop == propName);              // Return true or false
      }
    })                                          // The result is an array like [false, false, true, false]
    .reduce(function(previousValue, currentValue, index, array) {
      return previousValue || currentValue;
    }                                           // Do an 'or', or comparison of everything in the array.
                                                // It returns true if at least one value is true.
  )
}

deepCheck(object,'value'); // === true

PS: nem035's answer showed how it could be more performant: his solution breaks off at the first found 'value.'

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
wintvelt
  • 13,855
  • 3
  • 38
  • 43
3

My approach would be using try/catch blocks. Because I don't like to pass deep property paths in strings. I'm a lazy guy who likes autocompletion :)

JavaScript objects are evaluated on runtime. So if you return your object statement in a callback function, that statement is not going to be evaluated until callback function is invoked.

So this function just wraps the callback function inside a try catch statement. If it catches the exception returns false.

var obj = {
  innerObject: {
    deepObject: {
      value: 'Here am I'
    }
  }
};

const validate = (cb) => {
  try {
    return cb();
  } catch (e) {
    return false;
  }
}


if (validate(() => obj.innerObject.deepObject.value)) {
 // Is going to work
}


if (validate(() => obj.x.y.z)) {
 // Is not going to work
}

When it comes to performance, it's hard to say which approach is better. On my tests if the object properties exist and the statement is successful I noticed using try/catch can be 2x 3x times faster than splitting string to keys and checking if keys exist in the object.

But if the property doesn't exist at some point, prototype approach returns the result almost 7x times faster.

See the test yourself: https://jsfiddle.net/yatki/382qoy13/2/

You can also check the library I wrote here: https://github.com/yatki/try-to-validate

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mehmatrix
  • 2,028
  • 1
  • 15
  • 11
1

I use try-catch:

var object = {
   innerObject:{
       deepObject:{
           value:'Here am I'
       }
   }
};
var object2 = {
  a: 10
}

let exist = false, exist2 = false;

try {
  exist  = !!object.innerObject.deepObject.value
  exist2 = !!object2.innerObject.deepObject.value
}
catch(e) {
}

console.log(exist);
console.log(exist2);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DanielK
  • 792
  • 1
  • 6
  • 13
0

Try this nice and easy solution:

public hasOwnDeepProperty(obj, path)
{
    for (var i = 0, path = path.split('.'), len = path.length; i < len; i++)
    {
        obj = obj[path[i]];
        if (!obj) return false;
    };
    return true;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

In case you are writing JavaScript for Node.js, then there is an assert module with a 'deepEqual' method:

const assert = require('assert');
assert.deepEqual(testedObject, {
   innerObject:{
      deepObject:{
          value:'Here am I'
      }
   }
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rolf
  • 5,550
  • 5
  • 41
  • 61
0

I have created a very simple function for this using the recursive and happy flow coding strategy. It is also nice to add it to the Object.prototype (with enumerate:false!!) in order to have it available for all objects.

function objectHasOwnNestedProperty(obj, keys)
{
  if (!obj || typeof obj !== 'object')
  {
    return false;
  }

  if(typeof keys === 'string')
  {
    keys = keys.split('.');
  }

  if(!Array.isArray(keys))
  {
    return false;
  }

  if(keys.length == 0)
  {
    return Object.keys(obj).length > 0;
  }

  var first_key = keys.shift();

  if(!obj.hasOwnProperty(first_key))
  {
    return false;
  }

  if(keys.length == 0)
  {
    return true;
  }

  return objectHasOwnNestedProperty(obj[first_key],keys);
}

Object.defineProperty(Object.prototype, 'hasOwnNestedProperty',
{
    value: function () { return objectHasOwnNestedProperty(this, ...arguments); },
    enumerable: false
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mark Baaijens
  • 496
  • 5
  • 6