34

Given a string for a object property path, how do I set this property dynamically.

Given this sample object:

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

It should be able to set the value with a helper function like this:

setToValue(obj, 'After', 'a.b.0.c');

I tried it with the following code. But parent is a copy if the variable not a reference.

function setToValue(obj, value, path) {
    var arrPath = path.split('.'),
        parent = obj;

    for (var i = 0, max = arrPath.length; i < max; i++) {
        parent = parent[arrPath[i]];
    }

    parent = value;
}
Rob
  • 3,333
  • 5
  • 28
  • 71
Alex
  • 2,710
  • 1
  • 24
  • 26
  • Also note that in the above code I believe you are putting the max variable on the global scope since it's not declared beforehand through var.. So even when the function returns your max variable stays around.. – Tigraine Jul 28 '11 at 11:32
  • https://stackoverflow.com/a/43117598/385273 wins – Ben Aug 15 '21 at 02:36

3 Answers3

52

a) What's wrong with a simple a.b[0].c = 'After'?

As for the method:

function setToValue(obj, value, path) {
    var i;
    path = path.split('.');
    for (i = 0; i < path.length - 1; i++)
        obj = obj[path[i]];

    obj[path[i]] = value;
}

Here the JSFiddle: http://jsfiddle.net/QycBz/24/

t.niese
  • 39,256
  • 9
  • 74
  • 101
Tigraine
  • 23,358
  • 11
  • 65
  • 110
  • Thanks I missed "parent[path[path.length-1]] = value;" – Alex Jul 27 '11 at 11:49
  • 1
    Actually it was the problem with parent = value.. Because you where modifying a local variable instead of the object you held a reference to .. – Tigraine Jul 27 '11 at 12:03
  • Tigraine, this is an old question... but what on earth is the "selector" variable doing in your example? I'm trying to rewrite it using CoffeeScript, so that's why I'm wondering... :) – Per Lundberg May 23 '13 at 18:56
  • @PerLundberg The selector is probably a leftover while I was experimenting around with the code.. It's safe to remove. I updated the answer code. – Tigraine May 23 '13 at 21:52
  • 1
    Wow, this was a great help Tigraine. I needed exactly this, but I did add and additional syntax type for arrays, so something like "a.b[2].c" for example is equivalent to "a.b.2.c" I forked your JSFiddle, which is here http://jsfiddle.net/wxrzM/1/ – kstubs Sep 06 '13 at 07:45
  • My latest jsfiddle and complete object destruction example: http://jsfiddle.net/kstubs/uuMR2/3/ – kstubs Sep 07 '13 at 06:20
32

Here is a full solution.

Also creates objects if they don't exist.

function setValue(obj, path, value) {
  var a = path.split('.')
  var o = obj
  while (a.length - 1) {
    var n = a.shift()
    if (!(n in o)) o[n] = {}
    o = o[n]
  }
  o[a[0]] = value
}

function getValue(obj, path) {
  path = path.replace(/\[(\w+)\]/g, '.$1')
  path = path.replace(/^\./, '')
  var a = path.split('.')
  var o = obj
  while (a.length) {
    var n = a.shift()
    if (!(n in o)) return
    o = o[n]
  }
  return o
}
Gust van de Wal
  • 5,211
  • 1
  • 24
  • 48
Dieter Gribnitz
  • 5,062
  • 2
  • 41
  • 38
  • 1
    very nice code, but it ignores Array types (e.g: prop[0]) – Rafael Herscovici Mar 19 '16 at 10:24
  • could this be extended, like... `var obj = {}` `setValue(obj, 'a.b.c', 123)` `setValue(obj, 'a.b.d', 456)` `// expected: { a: { b: { c: 123, d: 456 } } }` `// actual: { a: { b: { d: 456 } } }` currently, calling the setValue for the second time does not extend the object – Ed Williams May 28 '17 at 14:27
4

FWIW, those of you wishing to the same in CoffeeScript might find these methods handy - it's a quite straight port of the above code. As an extra bonus, they make sure all the objects in the path exists (the getPropertyByPath doesn't throw exceptions if they don't, and the set method will create empty objects if any objects in the path happen to be null).

getPropertyByPath: (obj, path) ->
  path = path.split('.')
  parent = obj

  if path.length > 1
    parent = parent[path[i]] for i in [0..path.length - 2]

  parent?[path[path.length - 1]]

setPropertyByPath: (obj, path, value) ->
  path = path.split('.')
  parent = obj

  if path.length > 1
    parent = (parent[path[i]] ||= {}) for i in [0..path.length - 2]

  parent[path[path.length - 1]] = value
Per Lundberg
  • 3,837
  • 1
  • 36
  • 46