Skip to content Skip to sidebar Skip to footer

Test The Existence Of Property In A Deep Object Structure

In javascript, lets say I want to access a property deep in an object, for example: entry.mediaGroup[0].contents[0].url At any point along that structure, a property may be undefin

Solution 1:

This is a very lazy way to do it, but it meets the criteria for many similar situations:

try {
  console.log(entry.mediaGroup[0].contents[0].url);
} catch (e) {}

This should not be done on long code blocks where other errors may potentially be ignored, but should be suitable for a simple situation like this.


Solution 2:

/*decend through an object tree to a specified node, and return it.
  If node is unreachable, return undefined. This should also work with arrays in the tree.                                                                                               
  Examples:                                                                                                                                                                            
    var test1 = {a:{b:{c:{d:1}}}};                                                                                                                                            
    console.log(objectDesend(test1, 'a', 'b', 'c', 'd'));                                                                                                                
    var test2 = {a:{b:{c:1}}};     //will fail to reach d                                                                                                                                         
    console.log(objectDesend(test2, 'a', 'b', 'c', 'd'));
*/
var objectDescend = function(){
    var obj = arguments[0];
    var keys = arguments;
    var cur = obj;                                                                                                                                                        
    for(var i=1; i<keys.length; i++){                                                                                                                                     
        var key = keys[i];                                                                                                                                                
        var cur = cur[key];                                                                                                                                               
        if(typeof(cur)=='undefined')                                                                                                                                      
            return cur;                                                                                                                                                   
    }                                                                                                                                                                     
    return cur;                                                                                                                                                           
}                                                                                                                                                                         

var test1 = {a:{b:{c:{d:1}}}};                                                                                                                                            
console.log(objectDescend(test1, 'a', 'b', 'c', 'd'));                                                                                                                
var test2 = {a:{b:{c:1}}};                                                                                                                                              
console.log(objectDescend(test2, 'a', 'b', 'c', 'd'));

So this will return either the value you are looking for, or undefined since that value doesn't exist. It won't return false, as that may actually be the value you are looking for (d:false).

In my code base, I add Object.prototype.descend, so I can do test1.descend('a', 'b', 'c', 'd'). This will only work in ECMAScript 5 (IE>=9) since you need to make it so your function doesn't appear in enumerations. For more info: Add a method to Object primative, but not have it come up as a property

Here is my code for that:

Object.defineProperty(Object.prototype, 'descend', {
    value: function(){
        var keys = arguments;
        var cur = this;
        for(var i=0; i<keys.length; i++){
            var key = keys[i];
            var cur = cur[key];
            if(typeof(cur)=='undefined')
                return cur;
        }
        return cur;
    }
});



var test1 = {a:{b:{c:{d:false}}}};
//this will return false, which is the value of d                                                                                   
console.log(test1.descend('a', 'b', 'c', 'd'));                                                                                                                       
var test2 = {a:{b:{c:1}}};
//undefined since we can't reach d.                                                                                                
console.log(test2.descend(test2, 'a', 'b', 'c', 'd'));

Solution 3:

Your current solution is probably as good as you can get, as mVChr says, try..catch is just lazy here. It's probably far less effient and has nothing to recommend it other than perhaps being easier to type (but not significantly so) and it'll be harder to debug as it silently hides errors.

The real issue is the very long "reference worm" created by attempting such access. An alternative to the original that at least reduces the number of property lookups is:

var o;
if ( (o = entry       ) &&
     (o = o.mediaGroup) &&
     (o = o[0]        ) &&
     (o = o.contents  ) &&
     (o = o[0]        )) {
  alert(o.url);
}

But I expect you won't like that.

If you have many such deep access paths, you might like to create a function to do the access and return the last object on success or some other vaule on failure. For failure, you could also have it return the last non-falsey object on the path.

// Create test object
var entry = {};
entry.mediaGroup = [{
  contents: [{url: 'url'}]
}];

// Check that it "works" 
// alert(entry.mediaGroup[0].contents[0].url);


// Deep property access function, returns last object
// or false
function deepAccess(obj) {

  var path = arguments;
  var i = 0, iLen = path.length;
  var o = path[i++];  // o is first arg
  var p = path[i++];  // p is second arg

  // Go along path until o[p] is falsey
  while (o[p]) {
    o = o[p];
    p = path[i++];
  }

  // Return false if didn't get all the way along
  // the path or the last non-falsey value referenced
  return (--i == iLen) && o;
}

// Test it    
var x = deepAccess(entry, 'mediaGroup','0','contents','0');
alert(x && x.url);  // url

var x = deepAccess(entry, 'mediaGroup','1','contents','0');
alert(x && x.url);  // false

Solution 4:

There are probably 3-4 different questions along this vein, and four times as many answers. None of them really satisfied me, so I made my own, and I'll share it.

This function is called "deepGet".

Example:

deepGet(mySampleData, "foo.bar[2].baz", null);

Here is the full code:

function deepGet (obj, path, defaultValue) {

    // Split the path into components
    var a = path.split('.');

    // If we have just one component left, note that for later.
    var last = (a.length) === 1;

    // See if the next item is an array with an index
    var myregexp = /([a-zA-Z]+)(\[(\d+)\])+/; // matches:  item[0]
    var match = myregexp.exec(a[0]);

    // Get the next item
    var next;
    if (match !== null) {
        next = obj[match[1]];
        if (next !== undefined) {
            next = next[match[3]];
        }
    } else {
        next = obj[a[0]];
    }

    if (next === undefined || next === null) {
        // If we don't have what we want, return the default value
        return defaultValue;
    } else {
        if (last) {
            // If it's the last item in the path, return it
            return next; 
        } else { 
            // If we have more items in the path to go, recurse
            return deepGet (next, a.slice(1).join("."), defaultValue); 
        }
    }
}

Here is a jsFiddle: http://jsfiddle.net/7quzmjh8/2/

I was inspired by these two things:

http://designpepper.com/blog/drips/making-deep-property-access-safe-in-javascript.html http://jsfiddle.net/wxrzM/1/

Hopefully this is useful to someone out there :)


Solution 5:

I use this simple function for playing around with deep object properties:

getProperty = function(path) {
    try {
        return eval(path);
    }
    catch (e) {
        return undefined;
    }
};

Here's an example:

var test = {a:{b:{c:"success!"}}};

alert(getProperty('test.c.c'));
// undefined

alert(getProperty('test.a.b.c'));
// success!

Post a Comment for "Test The Existence Of Property In A Deep Object Structure"