user403428
user403428

Reputation: 93

Dynamic deep selection for a JavaScript object

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, ...)

Upvotes: 9

Views: 2502

Answers (4)

sym3tri
sym3tri

Reputation: 3823

I also just implemented this using an inner recursive function like so:

function get(obj, ns) {            

    function recurse(o, props) {
        if (props.length === 0) {
            return o;
        }
        if (!o) {
            return undefined;
        }
        return recurse(o[props.shift()], props);
    }

    return recurse(obj, ns.split('.'));
}

This will return the deep value of the property specified by the ns param, otherwise will always return undefined if it doesn't exist or there are any problems along the way.

Upvotes: 3

James
James

Reputation: 111960

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; //...

Upvotes: 13

Mike Clark
Mike Clark

Reputation: 11979

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.

Upvotes: 1

karim79
karim79

Reputation: 342695

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/

Upvotes: 0

Related Questions