Javascript object key value coding. Dynamically setting a nested value
Asked Answered
S

5

2

I'm working on a little library that lets me do some basic key value coding with objects. Say I have the following object:

var data = { key1: "value1", key2: { nested1: 1, nested2: "wowza!" } };

And I have the following JavaScript function:

var setData = function(path, value) {
    eval("data." + path + "= value;");
};

And in use:

setData("key1", "updated value1"); // data.key1 == "updated value1"
setData("key2.nested1", 99); // data.key2.nested1 == 99

This works, however I would like to accomplish the above without using eval. Is this possible, or is eval the best way to go?

EDIT:

NOTE it can be assumed the value you're setting exists, at least for path depth - 1. I'm more concerned about setting the value of an existing object.

Salzhauer answered 14/1, 2010 at 0:14 Comment(0)
E
18

Recursion is what you need:

function setData(key,val,obj) {
  if (!obj) obj = data; //outside (non-recursive) call, use "data" as our base object
  var ka = key.split(/\./); //split the key by the dots
  if (ka.length < 2) { 
    obj[ka[0]] = val; //only one part (no dots) in key, just set value
  } else {
    if (!obj[ka[0]]) obj[ka[0]] = {}; //create our "new" base obj if it doesn't exist
    obj = obj[ka.shift()]; //remove the new "base" obj from string array, and hold actual object for recursive call
    setData(ka.join("."),val,obj); //join the remaining parts back up with dots, and recursively set data on our new "base" obj
  }    
}

setData("key1", "updated value1"); // data.key1 == "updated value1"
setData("key2.nested1", 99); // data.key2.nested1 == 99
Evalyn answered 14/1, 2010 at 2:38 Comment(4)
I wish I could upvote this 100 times - it was exactly what I was looking for! Thank you!Dietetic
Thank you so very much! I can't begin to tell you how much this has helped me.Toper
This is a great answer. I made a version without obj argument and with ES6 syntax for legibility. gist.github.com/sungwoncho/8caf7f3d9e56741c0631cda134e01832Motel
Thank you so much. You save my day!Grindery
R
4

Yep, it's easy. obj.prop = val is the same as obj['prop'] = val so you can rewrite setData as this:

var setData = function (path, value) {
    data[path] = value;
};

And that should do it.

However, I am not sure how much success you will have with the 3rd level (if that makes sense.) obj.prop.prop can't be referenced by obj['prop.prop'] and will instead have to be referenced by obj['prop']['prop'], so setData would have to be rewritten to take that into account. I'm not having much luck with that, though.

Edit: I have made one that (for me) sets it nested how you want, but no further than that. Unfortunately, if you are nesting deeper than your examples then I don't see a real reason to abandon eval. I have not done any benchmarks, but at some point the calculations will be more computationally expensive than even eval (which is pretty hard to accomplish.)

This is what I came up with, which will set them at least 1 'level' deep; that is, it will work with key2.nested1 but not key2.nested1.i_love_nesting, if that makes sense:

var setData = function (path, value) {
    if (path.indexOf('.') != -1) {
        path = path.split('.');
        for (var i = 0, l = path.length; i < l; i++) {
            if (typeof(data[path[i]]) === 'object') {
                continue;
            } else {
                data[path[i - 1]][path[i]] = value;
            }
        }
    } else {
        data[path] = value;
    }
};

Hope this helps. I might not have written this in the most efficient way, though...

Roughandtumble answered 14/1, 2010 at 0:16 Comment(3)
Unfortunately this won't work for nested values. In your example: setData("prop.nestedProp", "val"); Will result in the following data json: { "prop.nestedProp": "val" } I want: { prop: { nestedProp: "val" } }Salzhauer
Yeah, I caught that a few seconds after I submitted the post. Took me a bit to get a nice, working function, but that one above should do. An alternative choice for your library might be to let them access the 'object' directly - i.e. let them access my_json_object.foo.bar.Roughandtumble
The full function (excluded for simplicity) will trigger an event if the value has changed. <code> app.dataSet = function(path, value) { if (app.dataGet(path) == value) return value; eval("app._data." + path + "= value;"); app.trigger("data." + path); return app.dataGet(path); }; </code> Unfortunately this needs to work at an arbitrary depth. I was hoping there might be a nice solution to the problem, let the get function (will have to test if it is faster then using eval too). Thanks for the help.Salzhauer
D
1

Inspired from @user1416920 I made this one :

function setData(key,val,obj)
{
    keys = key.split(/\./);
    last = keys.pop();

    keys.forEach(function(key)
    {
      if(typeof obj[key] === "undefined")
        obj[key] = {}; 
      obj = obj[key]; 
    });

    obj[last] = val;
}

What it does is pretty self explanatory ^^.

Duenas answered 30/12, 2016 at 0:16 Comment(0)
S
0
function setData(key,val,obj){
    keys = key.split(/\./);
    to_set = keys.pop();
    keys.forEach(function(obj_name){ obj = obj[obj_name]; });
    obj[to_set] = val;
}
Samira answered 25/5, 2012 at 8:33 Comment(0)
P
0

I think this problem becomes easier and more natural to solve if you can be flexible in how you're specifying your path. If instead of doing something like key2.nested1 you can do {key2:{nested1:{}}} (object notation), it becomes fairly trivial:

function setData(subtree, path){
    var nodeName = Object.keys(path);

    if(typeof path[nodeName] == 'object')
        setData(subtree[nodeName], path[nodeName]);
    else
        subtree[nodeName] = path[nodeName];
}

var data = { key1: "value1", key2: { nested1: 1, nested2: "wowza!" } };

// For the initial call, 'subtree' is the full data tree
setData(data, {key2:{nested1:99}});

You're simply recursing into the 2nd arg of the setData call, until you find the value. Each time you recurse, you step down into the data tree at the specified point.

This function can easily be modified to accept the dot notation you specified, too, but to me this is a cleaner and more natural approach (plus, String ops are slow).

Cheers

Photoconductivity answered 26/10, 2013 at 16:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.