1

say I have an object like this:

a : {
  a1 : {
    a2: true
  } 
}

and I have all the path saved in an array:

[a1, a2]

If I want to assign value to a["a1"]["a2"], it is easy:

a["a1"]["a2"] = true;

However when I have a 3 level path like this:

[a1, a2, a3]

I have to manually write the code like this:

a["a1"]["a2"]["a3"] = true;

Is there a way to automatically handle any level of paths so that I don't have to make it explicit for every single case?

Note that "a" can be quite complex so I only want to assign value to this specific element and without touching the rest.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Hypnos
  • 285
  • 1
  • 3
  • 10
  • 1
    I don't understand how the "path saved in an array" has to do with your question. Also, can you just type `a.a1.a2.a3`? Perhaps edit your question so you have a full function showing what you want to do, then a comment saying which line you think is too verbose that you want to minimize or change. – Mark Hildreth May 14 '13 at 00:46
  • possible duplicate of [Accessing nested JavaScript objects with string key](http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key) – Bergi May 14 '13 at 00:56
  • related: http://stackoverflow.com/a/10934946/989121 – georg May 14 '13 at 17:08

5 Answers5

5

You could iteratively traverse the object with the path like so:

function setDeepProperty(obj, path, value)
{
    var curr = obj;

    for (var depth = 0; depth < path.length - 1; depth++)
    {
        curr = curr[path[depth]];
    }

    curr[path[path.length - 1]] = value;
}

This assumes that the path is valid. Ensure that path[depth] in curr if necessary. The last step in the traversal is done outside of the loops because it would be setting curr to a primitive type instead of referencing an array (as we desire) meaning it wouldn't change the original. Then, as per your example:

var arr = {a1: {a2: { a3: false }}};
setDeepProperty(arr, ["a1", "a2", "a3"], true);

Note here that the nodes in the path are strings.

Elle
  • 3,695
  • 1
  • 17
  • 31
3

There are several ways you could access the properties:

Use a loop:

var obj = {
        a1 : {
            a2: { a3: 'test' }
        } 
    },
    i = 0,
    keyPath = ['a1', 'a2', 'a3'],
    len = keyPath.length;

    for (; i < len; i++) {
        obj = obj[keyPath[i]];
    }

    console.log(obj);

With eval (I don't recommend this however):

var obj = {
            a1 : {
                a2: { a3: 'test' }
            } 
        };

var value = eval('obj.' + keyPath.join('.'));

console.log(value);

You could use the same approach to set a property at a specific key path:

function setProperty(obj, keyPath, value) {
    var i = 0,
        len = keyPath.length - 1;

    for (; i < len; i++) {
        obj = obj[keyPath[i]];
    }

    obj[keyPath[i]] = value;
}
plalx
  • 42,889
  • 6
  • 74
  • 90
1

All are elegant solutions, my 2 cents with recursion:-

Test Here

var a = {
    a1: {
        a2: {
            a3: false
        }
    }
};

var path = ['a1', 'a2', 'a3'];

var valueToSet = true;
setValue(0, a);


function setValue(level, ob) {
  var prop = path[level];

  if (!ob.hasOwnProperty(prop)) {
    return;
  }
   if (level == (path.length - 1)) {
    ob[prop] = valueToSet;
    return;
   }

   return setValue(level + 1, ob[prop]);

}
console.log(a);
PSL
  • 123,204
  • 21
  • 253
  • 243
0

You have 2 possibilities:

  • the dreaded eval(). I refuse giving code for that
  • an in-out loop:

Code:

var a={
  a1 : {
    a2 : {
      a3: false
    } 
  } 
};
var idx=["a1", "a2", "a3"];

function recReplace(o, i, v) {
  var ii=i.shift();
  if (i.length==0)
    o[ii]=v;
  else
   o[ii]=recReplace(o[ii],i,v);
  return o;
}

b=recReplace(a,idx,true); //or: a=recReplace(a,idx,true);
Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
0

Sure, it's a simple loop:

var a = {a1:{a2:{}}};

var path = ["a1", "a2", "a3"];
for (var o=a, i=0; i<path.length-1; i++)
    o = o[path[i]]; // notice that this will throw exception
                    // when the objects do not exist already
o[path[i]] = true;
georg
  • 211,518
  • 52
  • 313
  • 390
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • much worse, it does NOT throw if a key exist, but isn't an object (check `{a1:{a2:999}}`). – georg May 14 '13 at 17:16
  • Yeah, for that you'd need to use `if (Object(o) !== o) throw new TypeError("Did encounter non-object, not going to get/set properties on this");`. – Bergi May 14 '13 at 17:22