Related
I hope someone can help me with this Javascript.
I have an Object called "Settings" and I would like to write a function that adds new settings to that object.
The new setting's name and value are provided as strings. The string giving the setting's name is then split by the underscores into an array. The new setting should get added to the existing "Settings" object by creating new nested objects with the names given by each part of the array, except the last part which should be a string giving the setting's value. I should then be able to refer to the setting and e.g. alert its value. I can do this in a static way like this...
var Settings = {};
var newSettingName = "Modules_Video_Plugin";
var newSettingValue = "JWPlayer";
var newSettingNameArray = newSettingName.split("_");
Settings[newSettingNameArray[0]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]][newSettingNameArray[2]] = newSettingValue;
alert(Settings.Modules.Mediaplayers.Video.Plugin);
... the part that creates the nested objects is doing this ...
Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";
However, as the number of parts that make up the setting name can vary, e.g. a newSettingName could be "Modules_Floorplan_Image_Src", I'd like to do this dynamically using a function such as...
createSetting (newSettingNameArray, newSettingValue);
function createSetting(setting, value) {
// code to create new setting goes here
}
Can anyone help me work out how to do this dynamically?
I presume there has to be a for...loop in there to itterate through the array, but I haven't been able to work out a way to create the nested objects.
If you've got this far thanks very much for taking the time to read even if you can't help.
Put in a function, short and fast (no recursion).
var createNestedObject = function( base, names ) {
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
};
// Usage:
createNestedObject( window, ["shapes", "triangle", "points"] );
// Now window.shapes.triangle.points is an empty object, ready to be used.
It skips already existing parts of the hierarchy. Useful if you are not sure whether the hierarchy was already created.
Or:
A fancier version where you can directly assign the value to the last object in the hierarchy, and you can chain function calls because it returns the last object.
// Function: createNestedObject( base, names[, value] )
// base: the object on which to create the hierarchy
// names: an array of strings contaning the names of the objects
// value (optional): if given, will be the last object in the hierarchy
// Returns: the last object in the hierarchy
var createNestedObject = function( base, names, value ) {
// If a value is given, remove the last name and keep it for later:
var lastName = arguments.length === 3 ? names.pop() : false;
// Walk the hierarchy, creating new objects where needed.
// If the lastName was removed, then the last object is not set yet:
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
// If a value was given, set it to the last name:
if( lastName ) base = base[ lastName ] = value;
// Return the last object in the hierarchy:
return base;
};
// Usages:
createNestedObject( window, ["shapes", "circle"] );
// Now window.shapes.circle is an empty object, ready to be used.
var obj = {}; // Works with any object other that window too
createNestedObject( obj, ["shapes", "rectangle", "width"], 300 );
// Now we have: obj.shapes.rectangle.width === 300
createNestedObject( obj, "shapes.rectangle.height".split('.'), 400 );
// Now we have: obj.shapes.rectangle.height === 400
Note: if your hierarchy needs to be built from values other that standard objects (ie. not {}), see also TimDog's answer below.
Edit: uses regular loops instead of for...in loops. It's safer in cases where a library modifies the Array prototype.
function assign(obj, keyPath, value) {
lastKeyIndex = keyPath.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
key = keyPath[i];
if (!(key in obj)){
obj[key] = {}
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
Usage:
var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
My ES2015 solution. Keeps existing values.
const set = (obj, path, val) => {
const keys = path.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((obj, key) =>
obj[key] = obj[key] || {},
obj);
lastObj[lastKey] = val;
};
Example:
const obj = {'a': {'prop': {'that': 'exists'}}};
set(obj, 'a.very.deep.prop', 'value');
console.log(JSON.stringify(obj));
// {"a":{"prop":{"that":"exists"},"very":{"deep":{"prop":"value"}}}}
Using ES6 is shorten. Set your path into an array.
first, you have to reverse the array, to start filling the object.
let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();
const nestedObject = obj.reduce((prev, current) => (
{[current]:{...prev}}
), {});
Another recursive solution:
var nest = function(obj, keys, v) {
if (keys.length === 1) {
obj[keys[0]] = v;
} else {
var key = keys.shift();
obj[key] = nest(typeof obj[key] === 'undefined' ? {} : obj[key], keys, v);
}
return obj;
};
Example usage:
var dog = {bark: {sound: 'bark!'}};
nest(dog, ['bark', 'loudness'], 66);
nest(dog, ['woff', 'sound'], 'woff!');
console.log(dog); // {bark: {loudness: 66, sound: "bark!"}, woff: {sound: "woff!"}}
I love this ES6 immutable way to set certain value on nested field:
const setValueToField = (fields, value) => {
const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
return fields.reduceRight(reducer, {});
};
And then use it with creating your target object.
const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }
Lodash has a _.set method to achieve this
let obj = {}
_.set(obj, ['a', 'b', 'c', 'd'], 'e')
or
_.set(obj, 'a.b.c.d', 'e')
// which generate the following object
{
"a": {
"b": {
"c": {
"d": "e"
}
}
}
}
Here is a simple tweak to jlgrall's answer that allows setting distinct values on each element in the nested hierarchy:
var createNestedObject = function( base, names, values ) {
for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};
Hope it helps.
Here is a functional solution to dynamically create nested objects.
const nest = (path, obj) => {
const reversedPath = path.split('.').reverse();
const iter = ([head, ...tail], obj) => {
if (!head) {
return obj;
}
const newObj = {[head]: {...obj}};
return iter(tail, newObj);
}
return iter(reversedPath, obj);
}
Example:
const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
Inspired by ImmutableJS setIn method which will never mutate the original. This works with mixed array and object nested values.
function setIn(obj = {}, [prop, ...rest], value) {
const newObj = Array.isArray(obj) ? [...obj] : {...obj};
newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
return newObj;
}
var obj = {
a: {
b: {
c: [
{d: 5}
]
}
}
};
const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");
//obj === {a: {b: {c: [{d: 5}]}}}
//newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
Appreciate that this question is mega old! But after coming across a need to do something like this in node, I made a module and published it to npm.
Nestob
var nestob = require('nestob');
//Create a new nestable object - instead of the standard js object ({})
var newNested = new nestob.Nestable();
//Set nested object properties without having to create the objects first!
newNested.setNested('biscuits.oblong.marmaduke', 'cheese');
newNested.setNested(['orange', 'tartan', 'pipedream'], { poppers: 'astray', numbers: [123,456,789]});
console.log(newNested, newNested.orange.tartan.pipedream);
//{ biscuits: { oblong: { marmaduke: 'cheese' } },
orange: { tartan: { pipedream: [Object] } } } { poppers: 'astray', numbers: [ 123, 456, 789 ] }
//Get nested object properties without having to worry about whether the objects exist
//Pass in a default value to be returned if desired
console.log(newNested.getNested('generic.yoghurt.asguard', 'autodrome'));
//autodrome
//You can also pass in an array containing the object keys
console.log(newNested.getNested(['chosp', 'umbridge', 'dollar'], 'symbols'));
//symbols
//You can also use nestob to modify objects not created using nestob
var normalObj = {};
nestob.setNested(normalObj, 'running.out.of', 'words');
console.log(normalObj);
//{ running: { out: { of: 'words' } } }
console.log(nestob.getNested(normalObj, 'random.things', 'indigo'));
//indigo
console.log(nestob.getNested(normalObj, 'improbable.apricots'));
//false
Inside your loop you can use lodash.set and will create the path for you:
...
const set = require('lodash.set');
const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');
console.log(p);
// { lang: { 'type': { 'name': '' }}}
try using recursive function:
function createSetting(setting, value, index) {
if (typeof index !== 'number') {
index = 0;
}
if (index+1 == setting.length ) {
settings[setting[index]] = value;
}
else {
settings[setting[index]] = {};
createSetting(setting, value, ++index);
}
}
I think, this is shorter:
Settings = {};
newSettingName = "Modules_Floorplan_Image_Src";
newSettingValue = "JWPlayer";
newSettingNameArray = newSettingName.split("_");
a = Settings;
for (var i = 0 in newSettingNameArray) {
var x = newSettingNameArray[i];
a[x] = i == newSettingNameArray.length-1 ? newSettingValue : {};
a = a[x];
}
I found #jlgrall's answer was great but after simplifying it, it didn't work in Chrome. Here's my fixed should anyone want a lite version:
var callback = 'fn.item1.item2.callbackfunction',
cb = callback.split('.'),
baseObj = window;
function createNestedObject(base, items){
$.each(items, function(i, v){
base = base[v] = (base[v] || {});
});
}
callbackFunction = createNestedObject(baseObj, cb);
console.log(callbackFunction);
I hope this is useful and relevant. Sorry, I've just smashed this example out...
You can define your own Object methods; also I'm using underscore for brevity:
var _ = require('underscore');
// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
var tmpo = this;
while (i = addr.shift())
tmpo = tmpo[i];
return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
this.pick(_.initial(addr))[_.last(addr)] = val;
};
Sample usage:
var obj = {
'foo': {
'bar': 0 }}
obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}
A snippet for those who need to create a nested objects with support of array keys to set a value to the end of path. Path is the string like: modal.product.action.review.2.write.survey.data. Based on jlgrall version.
var updateStateQuery = function(state, path, value) {
var names = path.split('.');
for (var i = 0, len = names.length; i < len; i++) {
if (i == (len - 1)) {
state = state[names[i]] = state[names[i]] || value;
}
else if (parseInt(names[i+1]) >= 0) {
state = state[names[i]] = state[names[i]] || [];
}
else {
state = state[names[i]] = state[names[i]] || {};
}
}
};
Set Nested Data:
function setNestedData(root, path, value) {
var paths = path.split('.');
var last_index = paths.length - 1;
paths.forEach(function(key, index) {
if (!(key in root)) root[key] = {};
if (index==last_index) root[key] = value;
root = root[key];
});
return root;
}
var obj = {'existing': 'value'};
setNestedData(obj, 'animal.fish.pet', 'derp');
setNestedData(obj, 'animal.cat.pet', 'musubi');
console.log(JSON.stringify(obj));
// {"existing":"value","animal":{"fish":{"pet":"derp"},"cat":{"pet":"musubi"}}}
Get Nested Data:
function getNestedData(obj, path) {
var index = function(obj, i) { return obj && obj[i]; };
return path.split('.').reduce(index, obj);
}
getNestedData(obj, 'animal.cat.pet')
// "musubi"
getNestedData(obj, 'animal.dog.pet')
// undefined
Try this: https://github.com/silkyland/object-to-formdata
var obj2fd = require('obj2fd/es5').default
var fd = obj2fd({
a:1,
b:[
{c: 3},
{d: 4}
]
})
Result :
fd = [
a => 1,
b => [
c => 3,
d => 4
]
]
Here is a decomposition to several useful functions, that each preserve existing data. Does not handle arrays.
setDeep: Answers question. Non-destructive to other data in the object.
setDefaultDeep: Same, but only sets if not already set.
setDefault: Sets a key if not already set. Same as Python's setdefault.
setStructure: Helper function that builds the path.
// Create a nested structure of objects along path within obj. Only overwrites the final value.
let setDeep = (obj, path, value) =>
setStructure(obj, path.slice(0, -1))[path[path.length - 1]] = value
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setDefaultDeep = (obj, path, value) =>
setDefault(setStructure(obj, path.slice(0, -1)), path[path.length - 1], value)
// Set obj[key] to value if key is not in object, and return obj[key]
let setDefault = (obj, key, value) =>
obj[key] = key in obj ? obj[key] : value;
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setStructure = (obj, path) =>
path.reduce((obj, segment) => setDefault(obj, segment, {}), obj);
// EXAMPLES
let temp = {};
// returns the set value, similar to assignment
console.log('temp.a.b.c.d:',
setDeep(temp, ['a', 'b', 'c', 'd'], 'one'))
// not destructive to 'one'
setDeep(temp, ['a', 'b', 'z'], 'two')
// does not overwrite, returns previously set value
console.log('temp.a.b.z: ',
setDefaultDeep(temp, ['a', 'b', 'z'], 'unused'))
// creates new, returns current value
console.log('temp["a.1"]: ',
setDefault(temp, 'a.1', 'three'))
// can also be used as a getter
console.log("temp.x.y.z: ",
setStructure(temp, ['x', 'y', 'z']))
console.log("final object:", temp)
I'm not sure why anyone would want string paths:
They are ambiguous for keys with periods
You have to build the strings in the first place
Since I started with something from this page, I wanted to contribute back
Other examples overwrote the final node even if it was set, and that wasn't what I wanted.
Also, if returnObj is set to true, it returns the base object. By default, falsy, it returns the deepest node.
function param(obj, path, value, returnObj) {
if (typeof path == 'string') path = path.split(".");
var child = obj;
path.forEach((key, i) => {
if (!(key in child)) {
child[key] = (i < path.length-1) ? {} : value || {};
}
child = child[key];
});
return returnObj ? obj : child;
}
var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
console.log(xOut);
You can also do something where numeric keys are placed in arrays (if they don't already exist). Note that numeric keys won't convert to arrays for the first element of the path, since that's set by the type of your base-object.
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function param(obj, path, value, returnObj) {
if (typeof path == 'string') path = path.split(".");
var child = obj;
path.forEach((key, i) => {
var nextKey = path[i+1];
if (!(key in child)) {
child[key] = (nextKey == undefined && value != undefined
? value
: isNumber(nextKey)
? []
: {});
}
child = child[key];
});
return returnObj ? obj : child;
}
var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
xOut = param(x, "1.0.2.a", "setting")
xOut = param(x, "1.0.1.a", "try to override") // won't set
xOut = param(x, "1.0.5.a", "new-setting", true) // get object rather than deepest node.
console.log(xOut);
Naturally, when the numeric keys are greater than 0, you might see some undefined gaps.
Practical uses of this might be
function AddNote(book, page, line) {
// assume a global global notes collection
var myNotes = param(allNotes, [book, page, line], []);
myNotes.push('This was a great twist!')
return myNotes;
}
var allNotes = {}
var youthfulHopes = AddNote('A Game of Thrones', 4, 2, "I'm already hooked, at least I won't have to wait long for the books to come out!");
console.log(allNotes)
// {"A Game of Thrones": [undefined, undefined, undefined, undefined, [undefined, undefined, ["I'm already hooked, at least I won't have to wait long for the books to come out!"]]]}
console.log(youthfulHopes)
// ["I'm already hooked, at least I won't have to wait long for the books to come out!"]
function initPath(obj, path) {
path.split('.').reduce((o, key) => (
Object.assign(o, {[key]: Object(o[key])}),
o[key]
), obj);
return obj;
}
Usage
const obj = { a: { b: 'value1' } };
initPath(obj, 'a.c.d').a.c.d='value2';
/*
{
"a": {
"b": "value1",
"c": {
"d": "value2"
}
}
}
*/
simple answer. on es6, im using this
const assign = (obj, path, value) => {
let keyPath = path.split('.')
let lastKeyIndex = keyPath.length - 1
for (let i = 0; i < lastKeyIndex; ++i) {
let key = keyPath[i]
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key]
}
obj[keyPath[lastKeyIndex]] = value
}
example json
const obj = {
b: 'hello'
}
you can add new key
assign(obj, 'c.d.e', 'this value')
and you get like bellow
console.log(obj)
//response example
obj = {
b: 'hello',
c: {
d: {
e: 'this value'
}
}
}
function createObj(keys, value) {
let obj = {}
let schema = obj
keys = keys.split('.')
for (let i = 0; i < keys.length - 1; i++) {
schema[keys[i]] = {}
schema = schema[keys[i]]
}
schema[keys.pop()] = value
return obj
}
let keys = 'value1.value2.value3'
let value = 'Hello'
let obj = createObj(keys, value)
Eval is probably overkill but the result is simple to visualize, with no nested loops or recursion.
function buildDir(obj, path){
var paths = path.split('_');
var final = paths.pop();
for (let i = 1; i <= paths.length; i++) {
var key = "obj['" + paths.slice(0, i).join("']['") + "']"
console.log(key)
eval(`${key} = {}`)
}
eval(`${key} = '${final}'`)
return obj
}
var newSettingName = "Modules_Video_Plugin_JWPlayer";
var Settings = buildDir( {}, newSettingName );
Basically you are progressively writing a string "obj['one']= {}", "obj['one']['two']"= {} and evaling it;
I have an object that could be any number of levels deep and could have any existing properties.
For example:
var obj = {
db: {
mongodb: {
host: 'localhost'
}
}
};
On that I would like to set (or overwrite) properties like so:
set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');
Where the property string can have any depth, and the value can be any type/thing.
Objects and arrays as values don't need to be merged, should the property key already exist.
Previous example would produce following object:
var obj = {
db: {
mongodb: {
host: 'localhost',
user: 'root'
}
},
foo: {
bar: baz
}
};
How can I realize such a function?
This function, using the arguments you specified, should add/update the data in the obj container. Note that you need to keep track of which elements in obj schema are containers and which are values (strings, ints, etc.) otherwise you will start throwing exceptions.
obj = {}; // global object
function set(path, value) {
var schema = obj; // a moving reference to internal objects within obj
var pList = path.split('.');
var len = pList.length;
for(var i = 0; i < len-1; i++) {
var elem = pList[i];
if( !schema[elem] ) schema[elem] = {}
schema = schema[elem];
}
schema[pList[len-1]] = value;
}
set('mongo.db.user', 'root');
Lodash has a _.set() method.
_.set(obj, 'db.mongodb.user', 'root');
_.set(obj, 'foo.bar', 'baz');
I just write a small function using ES6 + recursion to achieve the goal.
updateObjProp = (obj, value, propPath) => {
const [head, ...rest] = propPath.split('.');
!rest.length
? obj[head] = value
: this.updateObjProp(obj[head], value, rest.join('.'));
}
const user = {profile: {name: 'foo'}};
updateObjProp(user, 'fooChanged', 'profile.name');
I used it a lot on react to update state, it worked pretty well for me.
We can use a recursion function:
/**
* Sets a value of nested key string descriptor inside a Object.
* It changes the passed object.
* Ex:
* let obj = {a: {b:{c:'initial'}}}
* setNestedKey(obj, ['a', 'b', 'c'], 'changed-value')
* assert(obj === {a: {b:{c:'changed-value'}}})
*
* #param {[Object]} obj Object to set the nested key
* #param {[Array]} path An array to describe the path(Ex: ['a', 'b', 'c'])
* #param {[Object]} value Any value
*/
export const setNestedKey = (obj, path, value) => {
if (path.length === 1) {
obj[path] = value
return
}
return setNestedKey(obj[path[0]], path.slice(1), value)
}
It's more simple!
A bit late but here's a non-library, simpler answer:
/**
* Dynamically sets a deeply nested value in an object.
* Optionally "bores" a path to it if its undefined.
* #function
* #param {!object} obj - The object which contains the value you want to change/set.
* #param {!array} path - The array representation of path to the value you want to change/set.
* #param {!mixed} value - The value you want to set it to.
* #param {boolean} setrecursively - If true, will set value of non-existing path as well.
*/
function setDeep(obj, path, value, setrecursively = false) {
path.reduce((a, b, level) => {
if (setrecursively && typeof a[b] === "undefined" && level !== path.length){
a[b] = {};
return a[b];
}
if (level === path.length){
a[b] = value;
return value;
}
return a[b];
}, obj);
}
This function I made can do exactly what you need and a little more.
lets say we want to change the target value that is deeply nested in this object:
let myObj = {
level1: {
level2: {
target: 1
}
}
}
So we would call our function like so:
setDeep(myObj, ["level1", "level2", "target1"], 3);
will result in:
myObj = {
level1: {
level2: {
target: 3
}
}
}
Setting the set recursively flag to true will set objects if they don't exist.
setDeep(myObj, ["new", "path", "target"], 3, true);
will result in this:
obj = myObj = {
new: {
path: {
target: 3
}
},
level1: {
level2: {
target: 3
}
}
}
Inspired by #bpmason1's answer:
function leaf(obj, path, value) {
const pList = path.split('.');
const key = pList.pop();
const pointer = pList.reduce((accumulator, currentValue) => {
if (accumulator[currentValue] === undefined) accumulator[currentValue] = {};
return accumulator[currentValue];
}, obj);
pointer[key] = value;
return obj;
}
Example:
const obj = {
boats: {
m1: 'lady blue'
}
};
leaf(obj, 'boats.m1', 'lady blue II');
leaf(obj, 'boats.m2', 'lady bird');
console.log(obj); // { boats: { m1: 'lady blue II', m2: 'lady bird' } }
I came up with my own solution using pure es6 and recursion that doesn't mutate the original object.
const setNestedProp = (obj = {}, [first, ...rest] , value) => ({
...obj,
[first]: rest.length
? setNestedProp(obj[first], rest, value)
: value
});
const result = setNestedProp({}, ["first", "second", "a"],
"foo");
const result2 = setNestedProp(result, ["first", "second", "b"], "bar");
console.log(result);
console.log(result2);
Lodash has a method called update that does exactly what you need.
This method receives the following parameters:
The object to update
The path of the property to update (the property can be deeply nested)
A function that returns the value to update (given the original value as a parameter)
In your example it would look like this:
_.update(obj, 'db.mongodb.user', function(originalValue) {
return 'root'
})
ES6 has a pretty cool way to do this too using Computed Property Name and Rest Parameter.
const obj = {
levelOne: {
levelTwo: {
levelThree: "Set this one!"
}
}
}
const updatedObj = {
...obj,
levelOne: {
...obj.levelOne,
levelTwo: {
...obj.levelOne.levelTwo,
levelThree: "I am now updated!"
}
}
}
If levelThree is a dynamic property i.e. to set any of the property in levelTwo, you can use [propertyName]: "I am now updated!" where propertyName holds the name of the property in levelTwo.
I needed to achieve the same thing, but in Node.js...
So, I found this nice module: https://www.npmjs.com/package/nested-property
Example:
var mod = require("nested-property");
var obj = {
a: {
b: {
c: {
d: 5
}
}
}
};
console.log(mod.get(obj, "a.b.c.d"));
mod.set(obj, "a.b.c.d", 6);
console.log(mod.get(obj, "a.b.c.d"));
I created gist for setting and getting obj values by string based on correct answer. You can download it or use it as npm/yarn package.
// yarn add gist:5ceba1081bbf0162b98860b34a511a92
// npm install gist:5ceba1081bbf0162b98860b34a511a92
export const DeepObject = {
set: setDeep,
get: getDeep
};
// https://stackoverflow.com/a/6491621
function getDeep(obj: Object, path: string) {
path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
path = path.replace(/^\./, ''); // strip a leading dot
const a = path.split('.');
for (let i = 0, l = a.length; i < l; ++i) {
const n = a[i];
if (n in obj) {
obj = obj[n];
} else {
return;
}
}
return obj;
}
// https://stackoverflow.com/a/18937118
function setDeep(obj: Object, path: string, value: any) {
let schema = obj; // a moving reference to internal objects within obj
const pList = path.split('.');
const len = pList.length;
for (let i = 0; i < len - 1; i++) {
const elem = pList[i];
if (!schema[elem]) {
schema[elem] = {};
}
schema = schema[elem];
}
schema[pList[len - 1]] = value;
}
// Usage
// import {DeepObject} from 'somePath'
//
// const obj = {
// a: 4,
// b: {
// c: {
// d: 2
// }
// }
// };
//
// DeepObject.set(obj, 'b.c.d', 10); // sets obj.b.c.d to 10
// console.log(DeepObject.get(obj, 'b.c.d')); // returns 10
Extending the accepted answer provided by #bpmason1, to support arrays in string path e.g. string path can be 'db.mongodb.users[0].name' and 'db.mongodb.users[1].name'.
It will set the property value, which if doesn't exist, will be created.
var obj = {};
function set(path, value) {
var schema = obj;
var keysList = path.split('.');
var len = keysList.length;
for (var i = 0; i < len - 1; i++) {
var key = keysList[i];
// checking if key represents an array element e.g. users[0]
if (key.includes('[')) {
//getting propertyName 'users' form key 'users[0]'
var propertyName = key.substr(0, key.length - key.substr(key.indexOf("["), key.length - key.indexOf("[")).length);
if (!schema[propertyName]) {
schema[propertyName] = [];
}
// schema['users'][getting index 0 from 'users[0]']
if (!schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))]) {
// if it doesn't exist create and initialise it
schema = schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))] = {};
} else {
schema = schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))];
}
continue;
}
if (!schema[key]) {
schema[key] = {};
}
schema = schema[key];
} //loop ends
// if last key is array element
if (keysList[len - 1].includes('[')) {
//getting propertyName 'users' form key 'users[0]'
var propertyName = keysList[len - 1].substr(0, keysList[len - 1].length - keysList[len - 1].substr(keysList[len - 1].indexOf("["), keysList[len - 1].length - keysList[len - 1].indexOf("[")).length);
if (!schema[propertyName]) {
schema[propertyName] = [];
}
// schema[users][0] = value;
schema[propertyName][parseInt(keysList[len - 1].substr(keysList[len - 1].indexOf("[") + 1, keysList[len - 1].indexOf("]") - keysList[len - 1].indexOf("[") - 1))] = value;
} else {
schema[keysList[len - 1]] = value;
}
}
// will create if not exist
set("mongo.db.users[0].name.firstname", "hii0");
set("mongo.db.users[1].name.firstname", "hii1");
set("mongo.db.users[2].name", {
"firstname": "hii2"
});
set("mongo.db.other", "xx");
console.log(obj);
// will set if exist
set("mongo.db.other", "yy");
console.log(obj);
Here's a solution using ES 12
function set(obj = {}, key, val) {
const keys = key.split('.')
const last = keys.pop()
keys.reduce((o, k) => o[k] ??= {}, obj)[last] = val
}
(For older versions of javascript, you can do do o[k] || o[k] = {} in the reduce instead)
First, we set keys to be an array of everything but the last key.
Then in the reduce, the accumulator goes one level deeper into obj
each time, initializing it to an empty object if it the value at that key is not defined.
Finally, we set the value at the last key to val.
If you only need to change deeper nested objects, then another method could be to reference the object. As JS objects are handled by their references, you can create a reference to an object you have string-key access to.
Example:
// The object we want to modify:
var obj = {
db: {
mongodb: {
host: 'localhost',
user: 'root'
}
},
foo: {
bar: baz
}
};
var key1 = 'mongodb';
var key2 = 'host';
var myRef = obj.db[key1]; //this creates a reference to obj.db['mongodb']
myRef[key2] = 'my new string';
// The object now looks like:
var obj = {
db: {
mongodb: {
host: 'my new string',
user: 'root'
}
},
foo: {
bar: baz
}
};
Another approach is to use recursion to dig through the object:
(function(root){
function NestedSetterAndGetter(){
function setValueByArray(obj, parts, value){
if(!parts){
throw 'No parts array passed in';
}
if(parts.length === 0){
throw 'parts should never have a length of 0';
}
if(parts.length === 1){
obj[parts[0]] = value;
} else {
var next = parts.shift();
if(!obj[next]){
obj[next] = {};
}
setValueByArray(obj[next], parts, value);
}
}
function getValueByArray(obj, parts, value){
if(!parts) {
return null;
}
if(parts.length === 1){
return obj[parts[0]];
} else {
var next = parts.shift();
if(!obj[next]){
return null;
}
return getValueByArray(obj[next], parts, value);
}
}
this.set = function(obj, path, value) {
setValueByArray(obj, path.split('.'), value);
};
this.get = function(obj, path){
return getValueByArray(obj, path.split('.'));
};
}
root.NestedSetterAndGetter = NestedSetterAndGetter;
})(this);
var setter = new this.NestedSetterAndGetter();
var o = {};
setter.set(o, 'a.b.c', 'apple');
console.log(o); //=> { a: { b: { c: 'apple'}}}
var z = { a: { b: { c: { d: 'test' } } } };
setter.set(z, 'a.b.c', {dd: 'zzz'});
console.log(JSON.stringify(z)); //=> {"a":{"b":{"c":{"dd":"zzz"}}}}
console.log(JSON.stringify(setter.get(z, 'a.b.c'))); //=> {"dd":"zzz"}
console.log(JSON.stringify(setter.get(z, 'a.b'))); //=> {"c":{"dd":"zzz"}}
Late to the party - here's a vanilla js function that accepts a path as an argument and returns the modified object/json
let orig_json = {
string: "Hi",
number: 0,
boolean: false,
object: {
subString: "Hello",
subNumber: 1,
subBoolean: true,
subObject: {
subSubString: "Hello World"
},
subArray: ["-1", "-2", "-3"]
},
array: ["1", "2", "3"]
}
function changeValue(obj_path, value, json) {
let keys = obj_path.split(".")
let obj = { ...json },
tmpobj = {},
prevobj = {}
for (let x = keys.length - 1; x >= 0; x--) {
if (x == 0) {
obj[keys[0]] = tmpobj
} else {
let toeval = 'json.' + keys.slice(0, x).join('.');
prevobj = { ...tmpobj
}
tmpobj = eval(toeval);
if (x == keys.length - 1) tmpobj[keys[x]] = value
else {
tmpobj[keys[x]] = prevobj
}
}
}
return obj
}
let newjson = changeValue("object.subObject.subSubString", "Goodbye world", orig_json);
console.log(newjson)
Another solution to add or override properties:
function propertySetter(property, value) {
const sampleObject = {
string: "Hi",
number: 0,
boolean: false,
object: {
subString: "Hello",
subNumber: 1,
subBoolean: true,
subObject: {
subSubString: "Hello World",
},
subArray: ["-1", "-2", "-3"],
},
array: ["1", "2", "3"],
};
const keys = property.split(".");
const propertyName = keys.pop();
let propertyParent = sampleObject;
while (keys.length > 0) {
const key = keys.shift();
if (!(key in propertyParent)) {
propertyParent[key] = {};
}
propertyParent = propertyParent[key];
}
propertyParent[propertyName] = value;
return sampleObject;
}
console.log(propertySetter("object.subObject.anotherSubString", "Hello you"));
console.log(propertySetter("object.subObject.subSubString", "Hello Earth"));
console.log(propertySetter("object.subObject.nextSubString.subSubSubString", "Helloooo"));
Inspired by ImmutableJS setIn method which will never mutate the original.
This works with mixed array and object nested values.
function setIn(obj = {}, [prop, ...rest], value) {
const newObj = Array.isArray(obj) ? [...obj] : {...obj};
newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
return newObj;
}
var obj = {
a: {
b: {
c: [
{d: 5}
]
}
}
};
const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");
//obj === {a: {b: {c: [{d: 5}]}}}
//newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
As #aheuermann sed, you can use set from lodash library,
However, if you don't want to add lodash to your project for some reason you can use a recursion function that sets/overrides a value in an object.
/**
* recursion function that called in main function
* #param obj initial JSON
* #param keysList array of keys
* #param value value that you want to set
* #returns final JSON
*/
function recursionSet(obj, keysList, value) {
const key = keysList[0]
if (keysList.length === 1) return { ...obj, [key]: value }
return { ...obj, [key]: (recursionSet(obj?.[key] || {}, keysList.slice(1), value)) }
}
/**
* main function that you can call for set a value in an object by nested keys
* #param obj initial JSON
* #param keysString nested keys that seprated by "."
* #param value value that you want to set
* #returns final JSON
*/
function objectSet(obj, keysString, value) {
return recursionSet(obj, keysString.split('.'), value)
}
// simple usage
const a1 = {}
console.log('simple usage:', objectSet(a1, "b.c.d", 5))
// keep the initial data
const a2 = {b:{e: 8}}
console.log('keep the initial data:', objectSet(a2, "b.c.d", 5))
// override data
const a3 = {b:{e: 8, c:2}}
console.log('override data:', objectSet(a3, "b.c.d", 5))
// complex value
const a4 = {b:{e: 8, c:2}}
console.log('complex value:', objectSet(a4, "b.c.d", {f:12}))
If you would like a function that required prior properties to exist, then you could use something like this, it would also return a flag stating whether it managed to find and set the nested property.
function set(obj, path, value) {
var parts = (path || '').split('.');
// using 'every' so we can return a flag stating whether we managed to set the value.
return parts.every((p, i) => {
if (!obj) return false; // cancel early as we havent found a nested prop.
if (i === parts.length - 1){ // we're at the final part of the path.
obj[parts[i]] = value;
}else{
obj = obj[parts[i]]; // overwrite the functions reference of the object with the nested one.
}
return true;
});
}
JQuery has an extend method:
https://api.jquery.com/jquery.extend/
just pass the overwrites as an object and it will merge the two.
Inspired by ClojureScript's assoc-in (https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280), using recursion:
/**
* Associate value (v) in object/array (m) at key/index (k).
* If m is falsy, use new object.
* Returns the updated object/array.
*/
function assoc(m, k, v) {
m = (m || {});
m[k] = v;
return m;
}
/**
* Associate value (v) in nested object/array (m) using sequence of keys (ks)
* to identify the path to the nested key/index.
* If one of the values in the nested object/array doesn't exist, it adds
* a new object.
*/
function assoc_in(m={}, [k, ...ks], v) {
return ks.length ? assoc(m, k, assoc_in(m[k], ks, v)) : assoc(m, k, v);
}
/**
* Associate value (v) in nested object/array (m) using key string notation (s)
* (e.g. "k1.k2").
*/
function set(m, s, v) {
ks = s.split(".");
return assoc_in(m, ks, v);
}
Note:
With the provided implementation,
assoc_in({"a": 1}, ["a", "b"], 2)
returns
{"a": 1}
I would prefer that it throw an error in this case. If desired, you can add a check in assoc to verify m is either an object or array and throw an error otherwise.
I tried to write this set method in short, it may help someone!
function set(obj, key, value) {
let keys = key.split('.');
if(keys.length<2){ obj[key] = value; return obj; }
let lastKey = keys.pop();
let fun = `obj.${keys.join('.')} = {${lastKey}: '${value}'};`;
return new Function(fun)();
}
var obj = {
"hello": {
"world": "test"
}
};
set(obj, "hello.world", 'test updated');
console.log(obj);
set(obj, "hello.world.again", 'hello again');
console.log(obj);
set(obj, "hello.world.again.onece_again", 'hello once again');
console.log(obj);
const set = (o, path, value) => {
const props = path.split('.');
const prop = props.shift()
if (props.length === 0) {
o[prop] = value
} else {
o[prop] = o[prop] ?? {}
set(o[prop], props.join('.'), value)
}
}
in case you want to deeply update or insert an object
try this :-
let init = {
abc: {
c: {1: 2, 3: 5, 0: {l: 3}},
d: 100
}
}
Object.prototype.deepUpdate = function(update){
let key = Object.keys(update);
key.forEach((k) => {
if(typeof update[key] == "object"){
this[k].deepUpdate(update[key], this[k])
}
else
this[k] = update[k]
})
}
init.deepUpdate({abc: {c: {l: 10}}})
console.log(init)
but make sure it will change the original object, you can make it to not change the original object :
JSON.parse(JSON.stringify(init)).deepUpdate({abc: {c: {l: 10}}})
Improving on bpmason1's answer:
-adds a get() function.
-It does not require to define global storage object
-It is accessible from same domain iFrames
function set(path, value)
{
var schema = parent.document;
path="data."+path;
var pList = path.split('.');
var len = pList.length;
for(var i = 0; i < len-1; i++)
{
if(!schema[pList[i]])
schema[pList[i]] = {}
schema = schema[pList[i]];
}
schema[pList[len-1]] = value;
}
function get(path)
{
path="data."+path;
var schema=parent.document;
var pList = path.split('.');
for(var i = 0; i < pList.length; i++)
schema = schema[pList[i]];
return schema;
}
set('mongo.db.user', 'root');
set('mongo.db.name', 'glen');
console.log(get('mongo.db.name')); //prints 'glen'
Sometimes if the key also has dots (.) it its string this may pose a problem. As even that single key will now get split into various keys.
It is best to store the key path in an array, like so: ['db','mongodb','user'] and assign the value dynamically with the below function.
function set(obj, path, value) {
var schema = obj;
var pList = path.slice();
var len = pList.length;
for (var i = 0; i < len - 1; i++) {
var elem = pList[i];
if (!schema[elem]) schema[elem] = {};
schema = schema[elem];
}
schema[pList[len - 1]] = value;
}
let path = ['db','mongodb','user'];
set(obj, path, 'root');
I want to leave my answer for this interesting topic. Creating a function that sets dynamic properties for an object can be difficult.
const entity = {
haveDogs: true,
dogs: ['Maya', 'Perla']
}
function isObject(obj) {
return obj instanceof Object && obj.constructor === Object;
}
function setSchema(key, schema, value) {
if (!isObject(value)) {
schema[key] = value;
return
}
if (!schema[key]) schema[key] = {}
schema[key] = mutate(schema[key], value);
}
function mutate(obj, newObjData) {
const keys = Object.keys(newObjData)
for (const key of keys) {
let schema = obj
const list = key.split('.')
const value = newObjData[key]
const total = list.length - 1
if (list.length === 1) {
setSchema(key, schema, value)
continue
}
for (let i = 0; i < total; i++) {
const elem = list[i];
if (!schema[elem]) schema[elem] = {}
schema = schema[elem]
}
const subField = list[total]
setSchema(subField, schema, value)
}
return obj
}
mutate(entity, {
haveDogs: false,
'pet1.pet2.pet3.pet4.pet5': 'pets',
'bestFriends.list': ['Maya', 'Lucas'],
friends: {
'whitelist.permitted': ['Maya', 'Perla'],
'party.blocked': ['Juan', 'Trump']
}
})
console.log('[entity]', entity)
I have a deeply nested object and I want to manipulate a value of it and reassign it again. Is there a shorthand way for this other than writing it all out again or assigning it to a variable:
createStops[idx]['place']['create'][stop][key][value] = createStops[idx]['place']['create'][stop][key][value].toString()
looks ugly doesn't it? Something like:
createStops[idx]['place']['create'][stop][key][value].toStringAndReassign()
but JS built in.
Edit: In my case it is a number, if it's for your case too please check out #MarkMeyer answer.
No, there isn't.
Assigning a new value requires an assignment.
Strings are immutable, so you can't convert an existing value into a string in-place.
Given a value that's a number, if you just want it to be a string, you can coerce to a string with an assignment operator:
let o = {
akey: {
akey:{
value: 15
}
}
}
o.akey.akey.value += ''
console.log(o)
No,
Going to the same index is needed to store the value
Although it is not possible as mentioned by #Quentin you can define a custom getter in your object like:
var foo = {
a: 5,
b: 6,
get c () {
return this.b.toString()+' text'
}
};
console.log(foo.c);
You're not reassigning the value as you are semantically formatting your values. In order to format your value you are mutating your initial object. If you do not pretend to modify an object for formatting purposes that will work just fine.
You do not have integrated functions to use like that, but you could use of some utilitary functions of your own to help you manage assignements and make it less verbal.
SPOIL : The final use look like
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x + 1,
);
const createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5,
},
},
},
},
},
};
const idx = 'idx';
const stop = 'stop';
const key = 'key';
const value = 'value';
// Function that go to the specified key and
// execute a function on it.
// The new value is the result of the func
// You can do your toString there, or anything else
function executeFunctionAtKey(obj, path, func) {
const keys = path.split('.');
if (keys.length === 1) {
obj[path] = func(obj[key]);
return obj;
}
const lastPtr = keys.slice(0, keys.length - 1).reduce((tmp, x) => tmp[x], obj);
lastPtr[keys[keys.length - 1]] = func(lastPtr[keys[keys.length - 1]]);
return obj;
}
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x + 1,
);
console.log(createStops);
with the toString example from Number to String
const createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5,
},
},
},
},
},
};
const idx = 'idx';
const stop = 'stop';
const key = 'key';
const value = 'value';
// Function that go to the specified key and
// execute a function on it.
// The new value is the result of the func
// You can do your toString there, or anything else
function executeFunctionAtKey(obj, path, func) {
const keys = path.split('.');
if (keys.length === 1) {
obj[path] = func(obj[key]);
return obj;
}
const lastPtr = keys.slice(0, keys.length - 1).reduce((tmp, x) => tmp[x], obj);
lastPtr[keys[keys.length - 1]] = func(lastPtr[keys[keys.length - 1]]);
return obj;
}
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x.toString(),
);
console.log(createStops);
Theoretically you could build a function that takes an object, a path and the property to set it to.
This will reduce the readability of your code, so i would advice using ordinary assignment. But if you need it check out the snippet below:
//
function setProp(object, path, val) {
var parts = path.split("/").filter(function (p) { return p.length > 0; });
var pathIndex = 0;
var currentTarget = object;
while (pathIndex < parts.length - 1) {
currentTarget = currentTarget[parts[pathIndex]];
pathIndex++;
}
if (val instanceof Function) {
currentTarget[parts[pathIndex]] = val(currentTarget[parts[pathIndex]]);
}
else {
currentTarget[parts[pathIndex]] = val;
}
return object;
}
var createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5
}
}
}
}
}
};
function toString(p) { return p.toString(); }
console.log(JSON.stringify(createStops, null, 4));
setProp(createStops, 'idx/place/create/stop/key/value', toString);
console.log(JSON.stringify(createStops, null, 4));
UPDATE 1
Allowed passing functions and used OP JSON structure for snippet
I would like to have a function which modify a value in my JSON object. The value is specified by path like in this example example:
setConfigItem(['node1','nodeA','value1A'], 1234);
Currently I am using a following function:
var setConfigItem = function(path, value) {
if (path.length == 1)
config[path[0]] = value;
if (path.length == 2)
config[path[0]][path[1]] = value;
if (path.length == 3)
config[path[0]][path[1]][path[2]] = value;
// ...
throw {name : "NotImplementedError", message : "too lazy to implement more"};
}
What would be a better and generic solution?
Simple recursive solution
var config = {node1: {nodeA: {value1A: 0}}};
function setConfigItem(config, path, value) {
if (path.length == 1) {
config[path[0]] = value;
} else {
setConfigItem(config[path[0]], path.slice(1), value);
}
}
setConfigItem(config, ['node1','nodeA','value1A'], 1234);
console.log(config);
You can use Array#reduce to set your value dynamically using your path array.
Reduce will fold an array to a single value, we are seeding the reduce function with our object so each iteration we return currentObject[key] until we get to our desired key where we set the value.
const config = {
node1: {
nodeA: {
value1A: 4657
}
}
}
const setConfigItem = (path, value, obj) => {
// reduce the path array, each iteration dig further into the object properties
path.reduce((accumulator, key, i) => {
// if you are at the final key set the value
if (i === path.length - 1) {
accumulator[key] = value
return accumulator
}
// test to see if there is a property
if (typeof accumulator[key] === 'undefined') {
throw new Error('Nothing to see here')
}
// return the next level down
return accumulator[key]
}, obj)
// return the original object
return obj
}
console.log('before', config)
setConfigItem(['node1','nodeA','value1A'], 1234, config)
console.log('after', config)
expect('mutate given object with value', () => {
const obj = { a: 1 }
assert(setConfigItem(['a'], 2, obj)).deepEqual({ a: 2 })
assert(setConfigItem(['a'], 2, obj)).strictEqual(obj)
})
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>
A simple imperative solution:
const obj = {a: {b: {c: 1}}}
function updateAt(object, path, value) {
let o = object;
for(var i = 0; i < path.length; i++){
if(i < path.length - 1){
o = o[path[i]];
}
else{
o[path[i]] = value;
}
}
}
updateAt(obj, ['a', 'b', 'c'], 2);
console.log(obj);
You can use _.set() from Lodash:
let object = { a: { b: { c: 1 } } };
_.set(object, ['a', 'b', 'c'], 5);
console.log(object);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
This question already has answers here:
Convert a JavaScript string in dot notation into an object reference
(34 answers)
Closed 7 years ago.
Consider this object in javascript,
var obj = { a : { b: 1, c: 2 } };
given the string "obj.a.b" how can I get the object this refers to, so that I may alter its value? i.e. I want to be able to do something like
obj.a.b = 5;
obj.a.c = 10;
where "obj.a.b" & "obj.a.c" are strings (not obj references).
I came across this post where I can get the value the dot notation string is referring to obj but what I need is a way I can get at the object itself?
The nesting of the object may be even deeper than this. i.e. maybe
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
To obtain the value, consider:
function ref(obj, str) {
str = str.split(".");
for (var i = 0; i < str.length; i++)
obj = obj[str[i]];
return obj;
}
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
ref(obj, str) // 3
or in a more fancy way, using reduce:
function ref(obj, str) {
return str.split(".").reduce(function(o, x) { return o[x] }, obj);
}
Returning an assignable reference to an object member is not possible in javascript, you'll have to use a function like the following:
function set(obj, str, val) {
str = str.split(".");
while (str.length > 1)
obj = obj[str.shift()];
return obj[str.shift()] = val;
}
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
set(obj, str, 99)
console.log(obj.a.c.d) // 99
or use ref given above to obtain the reference to the containing object and then apply the [] operator to it:
parts = str.split(/\.(?=[^.]+$)/) // Split "foo.bar.baz" into ["foo.bar", "baz"]
ref(obj, parts[0])[parts[1]] = 99
Similar to thg435's answer, but with argument checks and supports nest levels where one of the ancestor levels isn't yet defined or isn't an object.
setObjByString = function(obj, str, val) {
var keys, key;
//make sure str is a string with length
if (!str || !str.length || Object.prototype.toString.call(str) !== "[object String]") {
return false;
}
if (obj !== Object(obj)) {
//if it's not an object, make it one
obj = {};
}
keys = str.split(".");
while (keys.length > 1) {
key = keys.shift();
if (obj !== Object(obj)) {
//if it's not an object, make it one
obj = {};
}
if (!(key in obj)) {
//if obj doesn't contain the key, add it and set it to an empty object
obj[key] = {};
}
obj = obj[key];
}
return obj[keys[0]] = val;
};
Usage:
var obj;
setObjByString(obj, "a.b.c.d.e.f", "hello");
If this javascript runs in a browser then you can access the object like this:
window['obj']['a']['b'] = 5
So given the string "obj.a.b" you have to split the it by .:
var s = "obj.a.b"
var e = s.split(".")
window[e[0]][e[1]][e[2]] = 5
Returning an assignable reference to an object member is not possible in javascript. You can assign value to a deep object member by dot notation with a single line of code like this.
new Function('_', 'val', '_.' + path + ' = val')(obj, value);
In you case:
var obj = { a : { b: 1, c: 2 } };
new Function('_', 'val', '_.a.b' + ' = val')(obj, 5); // Now obj.a.b will be equal to 5
var obj = { a : { b: 1, c: 2 } };
walkObject(obj,"a.b"); // 1
function walkObject( obj, path ){
var parts = path.split("."), i=0, part;
while (obj && (part=parts[i++])) obj=obj[part];
return obj;
}
Or if you like your code terse:
function walkObject( o, path ){
for (var a,p=path.split('.'),i=0; o&&(a=p[i++]); o=o[a]);
return o;
}
Below is a simple class wrapper around dict:
class Dots(dict):
def __init__(self, *args, **kargs):
super(Dots, self).__init__(*args, **kargs)
def __getitem__(self, key):
try:
item = super(Dots, self).__getitem__(key)
except KeyError:
item = Dots()
self.__setitem__(key, item)
return Dots(item) if type(item) == dict else item
def __setitem__(self, key, value):
if type(value) == dict: value = Dots(value)
super(Dots, self).__setitem__(key, value)
__getattr__ = __getitem__
__setattr__ = __setitem__
Example:
>>> a = Dots()
>>> a.b.c = 123
>>> a.b.c
123
>>> a.b
{'c': 123}
>>> a
{'b': {'c': 123}}
Missing key are created on the fly as empty Dots():
>>> if a.Missing: print "Exists"
...
>>> a
{'Missing': {}, 'b': {'c': 123}}