views:

331

answers:

2

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

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 w/o 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 JSON object.

+3  A: 

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

Reid
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" } }
tvongaza
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`.
Reid
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.
tvongaza
+2  A: 

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
Graza