最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - test the existence of property in a deep object structure - Stack Overflow

programmeradmin0浏览0评论

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 undefined (so mediaGroup may not be set).

What is a simple way to say:

if( entry.mediaGroup[0].contents[0].url ){
   console.log( entry.mediaGroup[0].contents[0].url )
}

without generating an error? This way will generate an undefined error if any point along the way is undefined.

My solution

if(entry) && (entry.mediaGroup) && (entry.MediaGroup[0]) ...snip...){
   console.log(entry.mediaGroup[0].contents[0].url)
}

which is pretty lengthy. I am guessing there must be something more elegant.

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 undefined (so mediaGroup may not be set).

What is a simple way to say:

if( entry.mediaGroup[0].contents[0].url ){
   console.log( entry.mediaGroup[0].contents[0].url )
}

without generating an error? This way will generate an undefined error if any point along the way is undefined.

My solution

if(entry) && (entry.mediaGroup) && (entry.MediaGroup[0]) ...snip...){
   console.log(entry.mediaGroup[0].contents[0].url)
}

which is pretty lengthy. I am guessing there must be something more elegant.

Share Improve this question edited Sep 21, 2012 at 16:00 StarPilot 2,2721 gold badge16 silver badges18 bronze badges asked Jun 1, 2011 at 22:35 RobKohrRobKohr 6,9437 gold badges56 silver badges81 bronze badges
Add a comment  | 

6 Answers 6

Reset to default 7

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.

/*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'));

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

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 :)

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!

Here's the one i have been using for a while

   var obj = { a: { b: [
                        { c: {d: 'XYZ'} }
                    ] } };

    // working

    obj.a.b[0].c.d = null;
    console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:null

    obj.a.b[0].c.d = 'XYZ';
    console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:XYZ
    console.log('value:'+getProperty(obj, 'a.b[0].c.d.k.sds', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE

    obj.a.b[0].c = null;
    console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE'));  // value:NOT-AVAILABLE


    // will not work
    //console.log('v:'+getProperty(obj, 'a.b["0"].c.d'));

Here's the function

function getProperty(obj, str, defaultValue){

    var props = str.split('.').map(function(prop){
        var arrAccessRegEx = /(.*)\[(.*)\]/g;
        if (arrAccessRegEx.test(prop)){
            return prop.split(arrAccessRegEx).filter(function(ele){return ele!=''; });
        } else {
            var retArr = [];
            retArr.push(prop);
            return retArr
        };
    });

    //console.log(props);

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

        //console.log('prop:'+prop);

        if (obj === null) return defaultValue;

        obj = obj[prop];

        if (obj === undefined) return defaultValue;

        if (props[i].length == 2){
            var idx = props[i][1];
            if (!(obj instanceof Array)) return defaultValue;
            if (idx < obj.length ){
                obj = obj[idx];
                if (obj === undefined) return defaultValue;
            }
        }

    } // for each item in split

    return obj;
}
发布评论

评论列表(0)

  1. 暂无评论