JS object has property deep check

Let's say we have JS 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')

Answers:

Answer

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'
       }
   }
}

obj.hasOwnNestedProperty('innerObject.deepObject.value');
Answer

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

Answer

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, returns array with true, false, false for any key with the name propName. The .reduce then rolls up 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
      } 
    })                                          // result is array like [false, false, true, false]
    .reduce(function(previousValue, currentValue, index, array) { 
      return previousValue || currentValue; 
    }                                           // do an or, or comparison of everything in array
                                                // returns true if at least one value is true
  )
}

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

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

Answer

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)) {
 // gonna work
}


if (validate(() => obj.x.y.z)) {
 // not gonna 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 spliting 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

Answer

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;
}
Answer

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

const assert = require('assert');
assert.deepEqual(testedObject, {
   innerObject:{
      deepObject:{
          value:'Here am I'
      }
   }
});
Answer

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);
Answer

I have created a very simple function for this using the recursive and happy flow coding strategy. 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
});

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.