38

Consider this object in javascript,

var obj = { a : { b: 1, c: 2 } };

given the string "obj.a.b" how can I get the object this refers to, so that I may alter its value? i.e. I want to be able to do something like

obj.a.b = 5;
obj.a.c = 10;

where "obj.a.b" & "obj.a.c" are strings (not obj references). I came across this post where I can get the value the dot notation string is referring to obj but what I need is a way I can get at the object itself?

The nesting of the object may be even deeper than this. i.e. maybe

var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
Machavity
  • 30,841
  • 27
  • 92
  • 100
source.rar
  • 8,002
  • 10
  • 50
  • 82

6 Answers6

77

To obtain the value, consider:

function ref(obj, str) {
    str = str.split(".");
    for (var i = 0; i < str.length; i++)
        obj = obj[str[i]];
    return obj;
}

var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
ref(obj, str) // 3

or in a more fancy way, using reduce:

function ref(obj, str) {
    return str.split(".").reduce(function(o, x) { return o[x] }, obj);
}

Returning an assignable reference to an object member is not possible in javascript, you'll have to use a function like the following:

function set(obj, str, val) {
    str = str.split(".");
    while (str.length > 1)
        obj = obj[str.shift()];
    return obj[str.shift()] = val;
}

var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
set(obj, str, 99)
console.log(obj.a.c.d) // 99

or use ref given above to obtain the reference to the containing object and then apply the [] operator to it:

parts = str.split(/\.(?=[^.]+$)/)  // Split "foo.bar.baz" into ["foo.bar", "baz"]
ref(obj, parts[0])[parts[1]] = 99
Redsandro
  • 11,060
  • 13
  • 76
  • 106
georg
  • 211,518
  • 52
  • 313
  • 390
  • Thanks the second part is what I was looking for and seems to be working fine. – source.rar Jun 07 '12 at 15:40
  • +1 Didn't see your `reduce` example at first. –  Jun 07 '12 at 16:30
  • I'd really appreciate if you'd elaborate on `set()` and the `split(/\.(?=[^.]+$)/)` example some more. – Redsandro Oct 30 '12 at 17:30
  • @Redsandro: hmm, do you have any questions? The code looks pretty straightforward to me. – georg Oct 30 '12 at 22:28
  • It's clear now. I was confused because in `set()` you use a different 'array traversing technique' than in the first `ref()` and I was trying to figure out why that was necesary - it's not, but it keeps things interesting. As for the last one, the regex confused me but I added comment to the code. – Redsandro Oct 31 '12 at 11:47
  • @Redsandro: for some reason your edit was rejected, but I added the comment nevertheless. – georg Oct 31 '12 at 11:54
  • Yeah somehow I have that effect on most moderators. Might have something to do with lack of rep. Anyway, thanks, you even improved the comment. – Redsandro Oct 31 '12 at 16:05
  • the fancy way should `return o && o[x]` instead of `return o[x]` this way it won't error when the path is invalid :) cheers – Shanimal Apr 06 '13 at 01:03
  • @Shanimal: if the path is invalid, it _should_ raise an error: "errors should never pass silently". – georg Apr 06 '13 at 07:59
  • no worries. in my case I wanted undefined returned with no error. i can raise an error just by trying to use the full path of the object. (e.g. `var a = {}; if(a.b.c){}`) – Shanimal Apr 06 '13 at 08:57
  • @georg—sure, but users should not see the errors so you should program to catch them. – RobG Jan 04 '15 at 23:08
  • spent a while looking for what you achieved in your `set` example. Brilliant - sending a sincere thanks! – scniro Dec 11 '15 at 22:34
  • This is fantastic. Reduce answer worked for me. Thanks very much. – iamdash Mar 17 '16 at 21:19
9

Similar to thg435's answer, but with argument checks and supports nest levels where one of the ancestor levels isn't yet defined or isn't an object.

setObjByString = function(obj, str, val) {
    var keys, key;
    //make sure str is a string with length
    if (!str || !str.length || Object.prototype.toString.call(str) !== "[object String]") {
        return false;
    }
    if (obj !== Object(obj)) {
        //if it's not an object, make it one
        obj = {};
    }
    keys = str.split(".");
    while (keys.length > 1) {
        key = keys.shift();
        if (obj !== Object(obj)) {
            //if it's not an object, make it one
            obj = {};
        }
        if (!(key in obj)) {
            //if obj doesn't contain the key, add it and set it to an empty object
            obj[key] = {};
        }
        obj = obj[key];
    }
    return obj[keys[0]] = val;
};

Usage:

var obj;
setObjByString(obj, "a.b.c.d.e.f", "hello");
  • would be excellent if you could figure out a way to write undefined properties, without overwriting sibling pre-defined properties – scniro Dec 12 '15 at 19:46
1

If this javascript runs in a browser then you can access the object like this:

window['obj']['a']['b'] = 5

So given the string "obj.a.b" you have to split the it by .:

var s = "obj.a.b"
var e = s.split(".")
window[e[0]][e[1]][e[2]] = 5
WojtekT
  • 4,735
  • 25
  • 37
1

Returning an assignable reference to an object member is not possible in javascript. You can assign value to a deep object member by dot notation with a single line of code like this.

new Function('_', 'val', '_.' + path + ' = val')(obj, value);

In you case:

var obj = { a : { b: 1, c: 2 } };

new Function('_', 'val', '_.a.b' + ' = val')(obj, 5); // Now obj.a.b will be equal to 5
Harish Ambady
  • 12,525
  • 4
  • 29
  • 54
0
var obj = { a : { b: 1, c: 2 } };
walkObject(obj,"a.b"); // 1

function walkObject( obj, path ){
  var parts = path.split("."), i=0, part;
  while (obj && (part=parts[i++])) obj=obj[part];
  return obj;
}

Or if you like your code terse:

function walkObject( o, path ){
  for (var a,p=path.split('.'),i=0; o&&(a=p[i++]); o=o[a]);
  return o;
}
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • Doesn't this just get me the value of the dot-notation string? And not the object itself which I can modify? – source.rar Jun 07 '12 at 15:34
  • @source.rar This returns you the value that is at the end of the chain, as shown on the second line. If that value is an object, then you will get an object back (e.g. `walkObject(obj,'a') // {b:1,c:2}`). – Phrogz Jun 07 '12 at 17:11
0

Below is a simple class wrapper around dict:

class Dots(dict):
    def __init__(self, *args, **kargs):
            super(Dots, self).__init__(*args, **kargs)

    def __getitem__(self, key):
            try:
                    item = super(Dots, self).__getitem__(key)
            except KeyError:
                    item = Dots()
                    self.__setitem__(key, item)

            return Dots(item) if type(item) == dict else item

    def __setitem__(self, key, value):
            if type(value) == dict: value = Dots(value)
            super(Dots, self).__setitem__(key, value)

    __getattr__ = __getitem__
    __setattr__ = __setitem__

Example:

>>> a = Dots()
>>> a.b.c = 123
>>> a.b.c
123
>>> a.b
{'c': 123}
>>> a
{'b': {'c': 123}}

Missing key are created on the fly as empty Dots():

>>> if a.Missing: print "Exists"
...
>>> a
{'Missing': {}, 'b': {'c': 123}}
bfontaine
  • 18,169
  • 13
  • 73
  • 107
Plamen
  • 9
  • 1