简体   繁体   中英

Update values in a json object based on dynamic keys available as array

I have a json object like this:

var obj = {
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "title": "S",
            "GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
                    "SortAs": "SGML",
                    "GlossTerm": "Standard Generalized Markup Language",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": ["GML", "XML"]
                    },
                    "GlossSee": "markup"
                }
            }
        }
    }
}

This JSON can be any valid JSON. Now I want to access and update some property based on an array available to me like this:

['glossary', 'GlossDiv', 'GlossList', 'GlossEntry', 'SortAs'] // any dynamic but valid path

So given the above array, I want to access

obj['glossary']['GlossDiv']['GlossList']['GlossEntry']['SortAs']

so that I can update its value. I have config(or any such json) as an instance variable, so I want to directly update that rather than creating a new json and then replacing the entire config object. I have access to jQuery, underscore, backbone and of course plain old JS.

You could reduce the object with walking the path.

 function getValue(object, path) { return path.reduce(function (o, k) { return (o || {})[k]; }, object); } var object = { glossary: { title: "example glossary", GlossDiv: { title: "S", GlossList: { GlossEntry: { ID: "SGML", SortAs: "SGML", GlossTerm: "Standard Generalized Markup Language", Acronym: "SGML", Abbrev: "ISO 8879:1986", GlossDef: { para: "A meta-markup language, used to create markup languages such as DocBook.", GlossSeeAlso: ["GML", "XML"] }, GlossSee: "markup" } } } } }; console.log(getValue(object, ['glossary', 'GlossDiv', 'GlossList', 'GlossEntry', 'SortAs'])); 

Though you are using Underscore, Lodash is 100% compatible with Backbone and can replace Underscore and it offers _.get and _.set for your specific case.

The path can be in an array or it can be a string. It even handles accessing array indexes.

_.get(object, path, [defaultValue])
 var object = { 'a': [{ 'b': { 'c': 3 } }] }; _.get(object, 'a[0].b.c'); // => 3 _.get(object, ['a', '0', 'b', 'c']); // => 3 _.get(object, 'abc', 'default'); // => 'default' 

_.set creates missing objects and arrays along the path if they do not exist.

_.set(object, path, value)
 _.set(object, 'a[0].b.c', 4); console.log(object.a[0].bc); // => 4 _.set(object, ['x', '0', 'y', 'z'], 5); console.log(object.x[0].yz); // => 5 

There's even a _.result function which calls the value if it's a function.

_.result(object, path, [defaultValue])
 var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; _.result(object, 'a[0].b.c1'); // => 3 _.result(object, 'a[0].b.c2'); // => 4 _.result(object, 'a[0].b.c3', 'default'); // => 'default' _.result(object, 'a[0].b.c3', _.constant('default')); // => 'default' 

Based on Nina's answer , to set the value, you could do something like the following, having a path which stops at before the last key, and use the last key to update the object manually.

 function getValue(object, path) { return path.reduce(function (o, k) { return (o || {})[k]; }, object); } var obj = { "glossary": { "title": "example glossary", "GlossDiv": { "title": "S", "GlossList": { "GlossEntry": { "ID": "SGML", "SortAs": "SGML", "GlossTerm": "Standard Generalized Markup Language", "Acronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDef": { "para": "A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso": ["GML", "XML"] }, "GlossSee": "markup" } } } } }; var target = getValue(obj, ['glossary', 'GlossDiv']); target.title = "test"; console.log(obj); 

Or as a new function, which takes a value directly and does all the job.

 function setValue(object, path, value) { var target = path.slice(0, -1).reduce(function(obj, key) { return (obj || {})[key]; }, object); target[path[path.length-1]] = value; } var obj = { "glossary": { "title": "example glossary", "GlossDiv": { "title": "S", "GlossList": { "GlossEntry": { "ID": "SGML", "SortAs": "SGML", "GlossTerm": "Standard Generalized Markup Language", "Acronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDef": { "para": "A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso": ["GML", "XML"] }, "GlossSee": "markup" } } } } }; setValue(obj, ['glossary', 'GlossDiv', 'GlossList', 'GlossEntry', 'GlossSee'], "my test value"); console.log(obj); 

Nina's answer would be my preferred way of doing this. For those not yet familiar with functional programming or reducing, a simple forEach will do the trick. Good for comparison.

var config = {
  name: "foo",
  email: "bar",
  photo: {
    small: "somelink",
    large: "largelink"
  }
};

var keys = ['photo', 'large'];
var prevRef = config, ref;

keys.forEach(function(val, index) {
  console.log(val);
  ref = prevRef[val];
  prevRef = ref;
});

console.log(ref);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM