views:

249

answers:

4

With a single property this is fairly easy:


var jsonobj = {
    "test": "ok"
}
var propname = "test";
// Will alert "ok"
alert(jsonobj[propname]);

But what I want to do is use a nested property:


var jsonobj = {
    "test": {
        "test2": "ok"
    }
}
var propname = "test.test2";
// Alerts undefined
alert(jsonobj[propname]);

Is there any way of selecting a nested "dynamic" property? I know I can do jsonobj.test.test2, but the problem is that propname can change to a property that goes 1,2 or 3 levels deep. (e.g test, test.test2, ...)

A: 

This works, but rather suckily uses eval so I'm not recommending it's use:

var jsonobj = {
    "test": {
        "test2": "ok"
    }
}
var propname = "test.test2";
alert(eval("jsonobj." + propname));
​

Try it here: http://jsfiddle.net/TAgsU/

karim79
You'll still get a TypeError if you try accessing a property of a null/undefined value.
J-P
...in addition to other potential catastrophes the above could lead to. That's why I've *disrecommended* it ;)
karim79
+1  A: 

You can write a little function to split the string and then access each piece in turn. For example:

function getProperty(propname, object)
{
    var props = propname.split('.');
    var obj = object;
    for (var i=0; i<props.length; i++)
    {
       obj = obj[props[i]];
    }
    return obj;
}

Obviously it nees a little extra coding to check for null objects, valid properties, etc.

Mike Clark
A: 

Treat it like a multi-dimensioned array...

var jsonobj = { 
  "test": { 
    "test2": "ok" 
  } 
} 

var a = "test"; 
var b = "test2"; 

alert(jsonobj[a][b]); 
Josh Stodola
+7  A: 
function resolve(cur, ns) {

    var undef;

    ns = ns.split('.');

    while (cur && ns[0])
        cur = cur[ns.shift()] || undef;

    return cur;

}

E.g.

// 1:
resolve({
    foo: { bar: 123 }
}, 'foo.bar'); // => 123


// 2:
var complex = {
    a: {
        b: [
            document.createElement('div')
        ]
    }
};

resolve(complex, 'a.b.0.nodeName'); // => DIV

The benefit in using this is that it won't throw an error if you try accessing something that doesn't exist -- it'll gracefully return undefined.


EDIT:

In the comment, Andy mentioned that this doesn't throw errors where one might expect it to. I agree that getting undefined is a little bit generic and there is no way to tell whether your value was really resolved. So, to remedy that, try this:

var resolve = (function(){

    var UNRESOLVED = resolve.UNRESOLVED = {};
    return resolve;

    function resolve(cur, ns) {

        var undef;

        ns = ns.split('.');

        while (cur && ns[0])
            cur = cur[ns.shift()] || undef;

        if (cur === undef || ns[0]) {
            return UNRESOLVED;
        }

        return cur;

    }

}());

It'll return an UNRESOLVED object that can be checked like so:

var result = resolve(someObject, 'a.b.c');

if (result === resolve.UNRESOLVED) {...}

It's not perfect, but it is (IMO) the best way to determine an unresolved namespace without having to throw errors. If you want errors, then just go ahead with:

someObject.a.b.c; //...
J-P
Gotta say, I quite like this. +1
karim79
This method is working fine for me, thanks a lot!
+1 Nicely done! I liked the `undefined` edit as well :)
Josh Stodola
The major downside to this is there's no error control. If I mistype something in the node path, `undefined` is returned and you're none the wiser. It's akin to using PHP's `@` operator for the same ends.
Andy E
@Andy, that is the nature of the solution. If you wanted errors then why not use regular lookup (`obj.a.b.c`)? Anyway, I've edited the answer with a possible remedy.
J-P
@J-P: the question asked for a solution that accepted a dynamic number of levels. There was no mention of error control. However, I can see certain situations where no error control could be useful, e.g. in web services that return JSON with a variable level of depth - I've worked with those before (eBay APIs spring to mind). Instead of having to recursively check that something exists you could use your method here and check for undefined instead, ignore it and move on.
Andy E
Won't this code return undef if the property value is "falsy" i.e 0 or false or '' or null? This may (or may not) be what is required.
Hans B PUFAL
This will obviously break if you have a property name that contains a full stop.
Tim Down