Convert javascript dot notation object to nested object - javascript

I'm trying to build a function that would expand an object like :
{
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
}
Into a nested object :
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
Like this php function : Set::expand()
Without using eval of course.

I believe this is what you're after:
function deepen(obj) {
const result = {};
// For each object path (property key) in the object
for (const objectPath in obj) {
// Split path into component parts
const parts = objectPath.split('.');
// Create sub-objects along path as needed
let target = result;
while (parts.length > 1) {
const part = parts.shift();
target = target[part] = target[part] || {};
}
// Set value at end of path
target[parts[0]] = obj[objectPath]
}
return result;
}
// For example ...
console.log(deepen({
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
}));

If you're using Node.js (e.g. - if not cut and paste out of our module), try this package: https://www.npmjs.org/package/dataobject-parser
Built a module that does the forward/reverse operations:
https://github.com/Gigzolo/dataobject-parser
It's designed as a self managed object right now. Used by instantiating an instance of DataObjectParser.
var structured = DataObjectParser.transpose({
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
});
structured.data() returns your nested object:
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
So here's a working example in JSFiddle:
http://jsfiddle.net/H8Cqx/

Function name is terrible and the code was quickly made, but it should work. Note that this modifies the original object, I am not sure if you wanted to create a new object that is expanded version of the old one.
(function(){
function parseDotNotation( str, val, obj ){
var currentObj = obj,
keys = str.split("."), i, l = keys.length - 1, key;
for( i = 0; i < l; ++i ) {
key = keys[i];
currentObj[key] = currentObj[key] || {};
currentObj = currentObj[key];
}
currentObj[keys[i]] = val;
delete obj[str];
}
Object.expand = function( obj ) {
for( var key in obj ) {
parseDotNotation( key, obj[key], obj );
}
return obj;
};
})();
var expanded = Object.expand({
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
});
JSON.stringify( expanded );
//"{"ab":{"cd":{"e":"foo","f":"bar"},"g":"foo2"}}"

Derived from Esailija's answer, with fixes to support multiple top-level keys.
(function () {
function parseDotNotation(str, val, obj) {
var currentObj = obj,
keys = str.split("."),
i, l = Math.max(1, keys.length - 1),
key;
for (i = 0; i < l; ++i) {
key = keys[i];
currentObj[key] = currentObj[key] || {};
currentObj = currentObj[key];
}
currentObj[keys[i]] = val;
delete obj[str];
}
Object.expand = function (obj) {
for (var key in obj) {
if (key.indexOf(".") !== -1)
{
parseDotNotation(key, obj[key], obj);
}
}
return obj;
};
})();
var obj = {
"pizza": "that",
"this.other": "that",
"alphabets": [1, 2, 3, 4],
"this.thing.that": "this"
}
Outputs:
{
"pizza": "that",
"alphabets": [
1,
2,
3,
4
],
"this": {
"other": "that",
"thing": {
"that": "this"
}
}
}
Fiddle

You could split the key string as path and reduce it for assigning the value by using a default object for unvisited levels.
function setValue(object, path, value) {
var keys = path.split('.'),
last = keys.pop();
keys.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
return object;
}
var source = { 'ab.cd.e': 'foo', 'ab.cd.f': 'bar', 'ab.g': 'foo2' },
target = Object
.entries(source)
.reduce((o, [k, v]) => setValue(o, k, v), {});
console.log(target);

You need to convert each string key into object. Using following function you can get desire result.
function convertIntoJSON(obj) {
var o = {}, j, d;
for (var m in obj) {
d = m.split(".");
var startOfObj = o;
for (j = 0; j < d.length ; j += 1) {
if (j == d.length - 1) {
startOfObj[d[j]] = obj[m];
}
else {
startOfObj[d[j]] = startOfObj[d[j]] || {};
startOfObj = startOfObj[d[j]];
}
}
}
return o;
}
Now call this function
var aa = {
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
};
var desiredObj = convertIntoJSON(aa);

Something that works, but is probably not the most efficient way to do so (also relies on ECMA 5 Object.keys() method, but that can be easily replaced.
var input = {
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
};
function createObjects(parent, chainArray, value) {
if (chainArray.length == 1) {
parent[chainArray[0]] = value;
return parent;
}
else {
parent[chainArray[0]] = parent[chainArray[0]] || {};
return createObjects(parent[chainArray[0]], chainArray.slice(1, chainArray.length), value);
}
}
var keys = Object.keys(input);
var result = {};
for(var i = 0, l = keys.length; i < l; i++)
{
createObjects(result, keys[i].split('.'), input[keys[i]]);
}
JSFiddle is here.

Here is how I do this in one of my applications:
const obj = {
"start.headline": "1 headline",
"start.subHeadline": "subHeadline",
"start.accordion.headline": "2 headline",
"start.accordion.sections.0.content": "content 0",
"start.accordion.sections.0.iconName": "icon 0",
"start.accordion.sections.1.headline": "headline 1",
"start.accordion.sections.1.content": "content 1",
"start.accordion.sections.1.iconName": "icon 1",
"start.accordion.sections.2.headline": "headline 2",
"start.accordion.sections.2.content": "content 2",
"start.accordion.sections.2.iconName": "icon 2",
"end.field": "add headline",
"end.button": "add button",
"end.msgs.success": "success msg",
"end.msgs.error": "error msg",
};
const res = Object.keys(obj).reduce((res, key) => {
const path = key.split('.');
const lastIndex = path.length - 1;
path.reduce(
(acc, k, i, a) => acc[k] = lastIndex === i ?
obj[key] :
acc[k] || (/\d/.test(a[i+1]) ? [] : {}),
res
);
return res;
}, {});
console.log(res);

ES6 one-liner:
const data = {
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
}
const result = Object.entries(data).reduce((a,[p,v])=>
(p.split('.').reduce((b,k,i,r)=>(b[k]??=(i===r.length-1?v:{})),a),a),{})
console.log(result)

This is the answer as provided by #broofa, but converted to TypeScript.
type NestedObject = { [key: string]: any };
function objectify(obj: NestedObject): NestedObject {
const result: NestedObject = {};
for (const key in obj) {
let target: NestedObject = result;
const parts = key.split(".");
for (let j = 0; j < parts.length - 1; j++) {
const part = parts[j];
target = target[part] = target[part] || {};
}
target[parts[parts.length - 1]] = obj[key];
}
return result;
}

Related

Equivalent of Lodash _.set() in Underscore.js [duplicate]

Suppose we are only given
var obj = {};
var propName = "foo.bar.foobar";
How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")?
So I want to achieve this, while we only have the property name in a string:
obj.foo.bar.foobar = "hello world";
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
var obj = {},
propName = "foo.bar.foobar";
assign(obj, propName, "Value");
I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.
Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question
function setDeepValue(obj, value, path) {
if (typeof path === "string") {
var path = path.split('.');
}
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
Use:
var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');
edit: I've created a jsPerf.com testcase to compare the accepted answer with my version.
Turns out that my version is faster, especially when you go very deep.
http://jsfiddle.net/9YMm8/
var nestedObjectAssignmentFor = function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
tmpObj = tmpObj[propNames[i]] = i !== propLength ? {} : value;
}
return obj;
}
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");
​
​
All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:
var obj = {}
nestObject.set(obj, "a.b", "foo");
nestObject.get(obj, "a.b"); // returns foo
var nestedObject = {
set: function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if (i === propLength){
if(tmpObj[propNames[i]]){
tmpObj[propNames[i]] = value;
}else{
tmpObj[propNames[i]] = value;
}
}else{
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
tmpObj = tmpObj[propNames[i]] = {};
}
}
}
return obj;
},
get: function(obj, propString){
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
break;
}
}
return tmpObj;
}
};
Can also change functions to be an Oject.prototype method changing obj param to this:
Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } }
{}.setNested('a.c','foo')
Here is a get and set function i just compiled from a couple of threads + some custom code.
It will also create keys that don't exist on set.
function setValue(object, path, value) {
var a = path.split('.');
var o = object;
for (var i = 0; i < a.length - 1; i++) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
}
o[a[a.length - 1]] = value;
}
function getValue(object, path) {
var o = object;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
}
Here is a simple function to do that using reference.
function setValueByPath (obj, path, value) {
var ref = obj;
path.split('.').forEach(function (key, index, arr) {
ref = ref[key] = index === arr.length - 1 ? value : {};
});
return obj;
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = {},
propName = 'foo.bar.foobar',
value = 'hello world';
setValue(object, propName, value);
console.log(object);
Here's one that returns the updated object
function deepUpdate(value, path, tree, branch = tree) {
const last = path.length === 1;
branch[path[0]] = last ? value : branch[path[0]];
return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}
const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }
A very straightforward one.
This implementation should be very performant.
It avoids recursions, and function calls, while maintaining simplicity.
/**
* Set the value of a deep property, creating new objects as necessary.
* #param {Object} obj The object to set the value on.
* #param {String|String[]} path The property to set.
* #param {*} value The value to set.
* #return {Object} The object at the end of the path.
* #author github.com/victornpb
* #see https://stackoverflow.com/a/46060952/938822
* #example
* setDeep(obj, 'foo.bar.baz', 'quux');
*/
function setDeep(obj, path, value) {
const props = typeof path === 'string' ? path.split('.') : path;
for (var i = 0, n = props.length - 1; i < n; ++i) {
obj = obj[props[i]] = obj[props[i]] || {};
}
obj[props[i]] = value;
return obj;
}
/*********************** EXAMPLE ***********************/
const obj = {
hello : 'world',
};
setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');
console.log(obj);
// ⬇︎ Click "Run" below to see output
I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs
function setValueAtObjectPath(obj, pathString, newValue) {
// create an array (pathComponents) of the period-separated path components from pathString
var pathComponents = pathString.split('.');
// create a object (tmpObj) that references the memory of obj
var tmpObj = obj;
for (var i = 0; i < pathComponents.length; i++) {
// if not on the last path component, then set the tmpObj as the value at this pathComponent
if (i !== pathComponents.length-1) {
// set tmpObj[pathComponents[i]] equal to an object of it's own value
tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
// set tmpObj to reference tmpObj[pathComponents[i]]
tmpObj = tmpObj[pathComponents[i]]
// else (IS the last path component), then set the value at this pathComponent equal to newValue
} else {
// set tmpObj[pathComponents[i]] equal to newValue
tmpObj[pathComponents[i]] = newValue
}
}
// return your object
return obj
}
Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:
export function cloneAndPatch(obj, path, newValue, separator='.') {
let stack = Array.isArray(path) ? path : path.split(separator);
let newObj = _.clone(obj);
obj = newObj;
while (stack.length > 1) {
let property = stack.shift();
let sub = _.clone(obj[property]);
obj[property] = sub;
obj = sub;
}
obj[stack.shift()] = newValue;
return newObj;
}
Object.getPath = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
};
Object.setPath = function(o, p, v) {
var a = p.split('.');
var o = o;
for (var i = 0; i < a.length - 1; i++) {
if (a[i].indexOf('[') === -1) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
} else {
// Not totaly optimised
var ix = a[i].match(/\[.*?\]/g)[0];
var n = a[i].replace(ix, '');
o = o[n][ix.substr(1,ix.length-2)]
}
}
if (a[a.length - 1].indexOf('[') === -1) {
o[a[a.length - 1]] = v;
} else {
var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
var n = a[a.length - 1].replace(ix, '');
o[n][ix.substr(1,ix.length-2)] = v;
}
};
Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.
function setObjectValueByPath(pathScope, value, obj) {
const pathStrings = pathScope.split('/');
obj[pathStrings[0]] = pathStrings.length > 1 ?
setObjectValueByPath(
pathStrings.splice(1, pathStrings.length).join('/'),
value,
obj[pathStrings[0]]
) :
value;
return obj;
}
How about a simple and short one?
Object.assign(this.origin, { [propName]: value })
You can use reduce : (you can test it by copy/paste on browser console)
const setValueOf = (obj, value, ...path) => {
path.reduce((o, level, idx) => {
if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
return o && o[level]; // return the prop
}, obj);
};
Example
let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};
setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'
You can split the string path by '.' and spread like :
setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));

How can I use data deep inside Json? [duplicate]

I'm trying to build a function that would expand an object like :
{
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
}
Into a nested object :
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
Like this php function : Set::expand()
Without using eval of course.
I believe this is what you're after:
function deepen(obj) {
const result = {};
// For each object path (property key) in the object
for (const objectPath in obj) {
// Split path into component parts
const parts = objectPath.split('.');
// Create sub-objects along path as needed
let target = result;
while (parts.length > 1) {
const part = parts.shift();
target = target[part] = target[part] || {};
}
// Set value at end of path
target[parts[0]] = obj[objectPath]
}
return result;
}
// For example ...
console.log(deepen({
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
}));
If you're using Node.js (e.g. - if not cut and paste out of our module), try this package: https://www.npmjs.org/package/dataobject-parser
Built a module that does the forward/reverse operations:
https://github.com/Gigzolo/dataobject-parser
It's designed as a self managed object right now. Used by instantiating an instance of DataObjectParser.
var structured = DataObjectParser.transpose({
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
});
structured.data() returns your nested object:
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
So here's a working example in JSFiddle:
http://jsfiddle.net/H8Cqx/
Function name is terrible and the code was quickly made, but it should work. Note that this modifies the original object, I am not sure if you wanted to create a new object that is expanded version of the old one.
(function(){
function parseDotNotation( str, val, obj ){
var currentObj = obj,
keys = str.split("."), i, l = keys.length - 1, key;
for( i = 0; i < l; ++i ) {
key = keys[i];
currentObj[key] = currentObj[key] || {};
currentObj = currentObj[key];
}
currentObj[keys[i]] = val;
delete obj[str];
}
Object.expand = function( obj ) {
for( var key in obj ) {
parseDotNotation( key, obj[key], obj );
}
return obj;
};
})();
var expanded = Object.expand({
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
});
JSON.stringify( expanded );
//"{"ab":{"cd":{"e":"foo","f":"bar"},"g":"foo2"}}"
Derived from Esailija's answer, with fixes to support multiple top-level keys.
(function () {
function parseDotNotation(str, val, obj) {
var currentObj = obj,
keys = str.split("."),
i, l = Math.max(1, keys.length - 1),
key;
for (i = 0; i < l; ++i) {
key = keys[i];
currentObj[key] = currentObj[key] || {};
currentObj = currentObj[key];
}
currentObj[keys[i]] = val;
delete obj[str];
}
Object.expand = function (obj) {
for (var key in obj) {
if (key.indexOf(".") !== -1)
{
parseDotNotation(key, obj[key], obj);
}
}
return obj;
};
})();
var obj = {
"pizza": "that",
"this.other": "that",
"alphabets": [1, 2, 3, 4],
"this.thing.that": "this"
}
Outputs:
{
"pizza": "that",
"alphabets": [
1,
2,
3,
4
],
"this": {
"other": "that",
"thing": {
"that": "this"
}
}
}
Fiddle
You could split the key string as path and reduce it for assigning the value by using a default object for unvisited levels.
function setValue(object, path, value) {
var keys = path.split('.'),
last = keys.pop();
keys.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
return object;
}
var source = { 'ab.cd.e': 'foo', 'ab.cd.f': 'bar', 'ab.g': 'foo2' },
target = Object
.entries(source)
.reduce((o, [k, v]) => setValue(o, k, v), {});
console.log(target);
You need to convert each string key into object. Using following function you can get desire result.
function convertIntoJSON(obj) {
var o = {}, j, d;
for (var m in obj) {
d = m.split(".");
var startOfObj = o;
for (j = 0; j < d.length ; j += 1) {
if (j == d.length - 1) {
startOfObj[d[j]] = obj[m];
}
else {
startOfObj[d[j]] = startOfObj[d[j]] || {};
startOfObj = startOfObj[d[j]];
}
}
}
return o;
}
Now call this function
var aa = {
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
};
var desiredObj = convertIntoJSON(aa);
Something that works, but is probably not the most efficient way to do so (also relies on ECMA 5 Object.keys() method, but that can be easily replaced.
var input = {
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
};
function createObjects(parent, chainArray, value) {
if (chainArray.length == 1) {
parent[chainArray[0]] = value;
return parent;
}
else {
parent[chainArray[0]] = parent[chainArray[0]] || {};
return createObjects(parent[chainArray[0]], chainArray.slice(1, chainArray.length), value);
}
}
var keys = Object.keys(input);
var result = {};
for(var i = 0, l = keys.length; i < l; i++)
{
createObjects(result, keys[i].split('.'), input[keys[i]]);
}
JSFiddle is here.
Here is how I do this in one of my applications:
const obj = {
"start.headline": "1 headline",
"start.subHeadline": "subHeadline",
"start.accordion.headline": "2 headline",
"start.accordion.sections.0.content": "content 0",
"start.accordion.sections.0.iconName": "icon 0",
"start.accordion.sections.1.headline": "headline 1",
"start.accordion.sections.1.content": "content 1",
"start.accordion.sections.1.iconName": "icon 1",
"start.accordion.sections.2.headline": "headline 2",
"start.accordion.sections.2.content": "content 2",
"start.accordion.sections.2.iconName": "icon 2",
"end.field": "add headline",
"end.button": "add button",
"end.msgs.success": "success msg",
"end.msgs.error": "error msg",
};
const res = Object.keys(obj).reduce((res, key) => {
const path = key.split('.');
const lastIndex = path.length - 1;
path.reduce(
(acc, k, i, a) => acc[k] = lastIndex === i ?
obj[key] :
acc[k] || (/\d/.test(a[i+1]) ? [] : {}),
res
);
return res;
}, {});
console.log(res);
This is the answer as provided by #broofa, but converted to TypeScript.
type NestedObject = { [key: string]: any };
function objectify(obj: NestedObject): NestedObject {
const result: NestedObject = {};
for (const key in obj) {
let target: NestedObject = result;
const parts = key.split(".");
for (let j = 0; j < parts.length - 1; j++) {
const part = parts[j];
target = target[part] = target[part] || {};
}
target[parts[parts.length - 1]] = obj[key];
}
return result;
}

How to set value in Object

I have to set value in object.
This is the problem: when I try to write a prop in object that has some props, previous props will be deleted.
I solved two cases and there's the last one.
I need test2 to be saved.
How can I fix it?
Thanks.
var obj = {
keyOne: "foo",
keyTwo: {
test1: "baz",
test2: {
test21: ["bar"],
}
}
}
function setObjectProperty(obj, string, value) {
var path = string.split('.');
var currentObj = obj;
for (var i = 0; i < path.length - 1; i++) {
if (!currentObj[path[i]] || currentObj[path[i]] != "string") {
currentObj[path[i]] = {};
currentObj = currentObj[path[i]];
}
}
currentObj[path[path.length - 1]] = value;
};
setObjectProperty(obj, 'keyOne', 'new');
setObjectProperty(obj, 'keyOne.key.key2', 'newnew');
setObjectProperty(obj, 'keyTwo.test1', 'zzz');
console.log(obj);
Few corrections are made.
You need to advance the object path regardless of creating a child object
For checking string type, you need to use typeof
var obj = {
keyOne: "foo",
keyTwo: {
test1: "baz",
test2: {
test21: ["bar"],
}
}
}
function setObjectProperty(obj, string, value) {
var path = string.split('.');
var currentObj = obj;
for (var i = 0; i < path.length - 1; i++) {
if (!currentObj[path[i]] || typeof currentObj[path[i]] === 'string') {
currentObj[path[i]] = {};
}
currentObj = currentObj[path[i]];
}
currentObj[path[path.length - 1]] = value;
};
setObjectProperty(obj, 'keyOne', 'new');
setObjectProperty(obj, 'keyOne.key.key2', 'newnew');
setObjectProperty(obj, 'keyTwo.test1', 'zzz');
console.log(obj);
The test currentObj[path[i]] != "string" seems weird, I think you want to use typeof instead.
Inside of your loop, you should be advancing at every step but only creating an object if it does not exists.
Working code:
function setObjectProperty(obj, string, value) {
var path = string.split('.');
var currentObj = obj;
for (var i = 0; i < path.length - 1; i++) {
if (!currentObj[path[i]] || typeof currentObj[path[i]] == "string") {
currentObj[path[i]] = {};
}
currentObj = currentObj[path[i]];
}
currentObj[path[path.length - 1]] = value;
};
Is this what you are looking for?
const store = {
keyOne: "foo",
keyTwo: {
test1: "baz",
test2: {
test21: ["bar"],
}
}
}
function getKeys(key) {
return key.split(".").filter(k => k.length);
}
function assignProps(object, keys, value = null, root = null) {
root = root || object;
const key = keys.shift();
if (!key) return root;
object[key] = keys.length === 0 ? value : {};
assignProps(object[key], keys, value, root);
}
assignProps(store, getKeys("keyOne.foo.bar"), "baz");
assignProps(store, getKeys("keyTwo.test1.test2"), "value");
console.log(store);

Is there a way to assign value to deeply nested property of object using a string? [duplicate]

Suppose we are only given
var obj = {};
var propName = "foo.bar.foobar";
How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")?
So I want to achieve this, while we only have the property name in a string:
obj.foo.bar.foobar = "hello world";
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
var obj = {},
propName = "foo.bar.foobar";
assign(obj, propName, "Value");
I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.
Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question
function setDeepValue(obj, value, path) {
if (typeof path === "string") {
var path = path.split('.');
}
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
Use:
var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');
edit: I've created a jsPerf.com testcase to compare the accepted answer with my version.
Turns out that my version is faster, especially when you go very deep.
http://jsfiddle.net/9YMm8/
var nestedObjectAssignmentFor = function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
tmpObj = tmpObj[propNames[i]] = i !== propLength ? {} : value;
}
return obj;
}
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");
​
​
All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:
var obj = {}
nestObject.set(obj, "a.b", "foo");
nestObject.get(obj, "a.b"); // returns foo
var nestedObject = {
set: function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if (i === propLength){
if(tmpObj[propNames[i]]){
tmpObj[propNames[i]] = value;
}else{
tmpObj[propNames[i]] = value;
}
}else{
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
tmpObj = tmpObj[propNames[i]] = {};
}
}
}
return obj;
},
get: function(obj, propString){
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
break;
}
}
return tmpObj;
}
};
Can also change functions to be an Oject.prototype method changing obj param to this:
Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } }
{}.setNested('a.c','foo')
Here is a get and set function i just compiled from a couple of threads + some custom code.
It will also create keys that don't exist on set.
function setValue(object, path, value) {
var a = path.split('.');
var o = object;
for (var i = 0; i < a.length - 1; i++) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
}
o[a[a.length - 1]] = value;
}
function getValue(object, path) {
var o = object;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
}
Here is a simple function to do that using reference.
function setValueByPath (obj, path, value) {
var ref = obj;
path.split('.').forEach(function (key, index, arr) {
ref = ref[key] = index === arr.length - 1 ? value : {};
});
return obj;
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = {},
propName = 'foo.bar.foobar',
value = 'hello world';
setValue(object, propName, value);
console.log(object);
Here's one that returns the updated object
function deepUpdate(value, path, tree, branch = tree) {
const last = path.length === 1;
branch[path[0]] = last ? value : branch[path[0]];
return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}
const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }
A very straightforward one.
This implementation should be very performant.
It avoids recursions, and function calls, while maintaining simplicity.
/**
* Set the value of a deep property, creating new objects as necessary.
* #param {Object} obj The object to set the value on.
* #param {String|String[]} path The property to set.
* #param {*} value The value to set.
* #return {Object} The object at the end of the path.
* #author github.com/victornpb
* #see https://stackoverflow.com/a/46060952/938822
* #example
* setDeep(obj, 'foo.bar.baz', 'quux');
*/
function setDeep(obj, path, value) {
const props = typeof path === 'string' ? path.split('.') : path;
for (var i = 0, n = props.length - 1; i < n; ++i) {
obj = obj[props[i]] = obj[props[i]] || {};
}
obj[props[i]] = value;
return obj;
}
/*********************** EXAMPLE ***********************/
const obj = {
hello : 'world',
};
setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');
console.log(obj);
// ⬇︎ Click "Run" below to see output
I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs
function setValueAtObjectPath(obj, pathString, newValue) {
// create an array (pathComponents) of the period-separated path components from pathString
var pathComponents = pathString.split('.');
// create a object (tmpObj) that references the memory of obj
var tmpObj = obj;
for (var i = 0; i < pathComponents.length; i++) {
// if not on the last path component, then set the tmpObj as the value at this pathComponent
if (i !== pathComponents.length-1) {
// set tmpObj[pathComponents[i]] equal to an object of it's own value
tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
// set tmpObj to reference tmpObj[pathComponents[i]]
tmpObj = tmpObj[pathComponents[i]]
// else (IS the last path component), then set the value at this pathComponent equal to newValue
} else {
// set tmpObj[pathComponents[i]] equal to newValue
tmpObj[pathComponents[i]] = newValue
}
}
// return your object
return obj
}
Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:
export function cloneAndPatch(obj, path, newValue, separator='.') {
let stack = Array.isArray(path) ? path : path.split(separator);
let newObj = _.clone(obj);
obj = newObj;
while (stack.length > 1) {
let property = stack.shift();
let sub = _.clone(obj[property]);
obj[property] = sub;
obj = sub;
}
obj[stack.shift()] = newValue;
return newObj;
}
Object.getPath = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
};
Object.setPath = function(o, p, v) {
var a = p.split('.');
var o = o;
for (var i = 0; i < a.length - 1; i++) {
if (a[i].indexOf('[') === -1) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
} else {
// Not totaly optimised
var ix = a[i].match(/\[.*?\]/g)[0];
var n = a[i].replace(ix, '');
o = o[n][ix.substr(1,ix.length-2)]
}
}
if (a[a.length - 1].indexOf('[') === -1) {
o[a[a.length - 1]] = v;
} else {
var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
var n = a[a.length - 1].replace(ix, '');
o[n][ix.substr(1,ix.length-2)] = v;
}
};
Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.
function setObjectValueByPath(pathScope, value, obj) {
const pathStrings = pathScope.split('/');
obj[pathStrings[0]] = pathStrings.length > 1 ?
setObjectValueByPath(
pathStrings.splice(1, pathStrings.length).join('/'),
value,
obj[pathStrings[0]]
) :
value;
return obj;
}
How about a simple and short one?
Object.assign(this.origin, { [propName]: value })
You can use reduce : (you can test it by copy/paste on browser console)
const setValueOf = (obj, value, ...path) => {
path.reduce((o, level, idx) => {
if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
return o && o[level]; // return the prop
}, obj);
};
Example
let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};
setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'
You can split the string path by '.' and spread like :
setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));

Create a JavaScript object from keys which are dot separated in another object

I have a requirement where I have an object like obj={ 'a.b.c' : d }
and I would like it to get converted to {a:{b:{c:d}}}
Is there any way I can achieve this in JavaScript?
Here's a solution (EDITED: code is more complex than before but it gives the result you want, let me know if something doesn't work):
var obj = {
'a.b.c': 22,
'a.b.d.e': 42
}
var newObj = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var keyList = key.split('.');
newObj = generateNewObject(keyList, keyList.length - 1, newObj, obj[key]);
}
}
console.log(newObj);
function generateNewObject(keys, index, existingObj, value) {
if (index < 0) {
return value;
}
var lastKey = keys[index--];
var existingProperty = getProperty(existingObj, lastKey);
if (existingProperty != null && !objectsAreEqual(existingProperty, value)) {
var valueKey = keys[index + 2];
existingProperty[lastKey][valueKey] = value[valueKey];
value = existingProperty;
} else {
var subObj = {};
subObj[lastKey] = value;
value = subObj;
}
return generateNewObject(keys, index, existingObj, value);
}
function objectsAreEqual(obj1, obj2) {
for (var key in obj1) {
if (obj1.hasOwnProperty(key)) {
var prop = getProperty(obj2, key);
if (prop == null) {
return false;
}
}
}
return true;
}
function getProperty(obj, keyDesired) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (key === keyDesired) {
return obj;
} else {
var prop = getProperty(obj[key], keyDesired);
if (prop != null) {
return prop;
}
}
}
}
return null;
}
I don't know why you would have an object named that way, but this code will do the trick for each key in an object. This will not work correctly on nested objects such as {'a' : { 'b' { 'c' : {{'d' : 'e'}}}}}. You would have to repeat the for-loop part each time the value is a JavaScript object.
EDIT
I modified the code so it recognizes when two properties are the same such as the example { 'a.b.c' : 22 }, 'a.b.c.d.e' : 42. Sorry if it is hard to go through, but basically the generateNewObject method is the real meat of it. The two functions below it are just helper methods.
Array.reduce mostly is a good choice when it comes to handling/transforming of more complex data structures. An approach that solves the given problem generically whilst taking edge cases into account then might look similar to the next provided example ...
var
d = 'd',
q = 'q',
obj = {
'i.k.l.m.n.o.p' : q,
'a.b.c' : d,
'foo' : 'bar',
'' : 'empty'
};
function parseIntoNestedTypes(type) {
return Object.keys(type).reduce(function (collector, integralKey) {
var
nestedType = collector.target,
fragmentedKeyList = integralKey.split('.'),
nestedTypeRootKey = fragmentedKeyList.shift(),
nestedTypeEndValue = collector.source[integralKey];
if (fragmentedKeyList.length === 0) {
nestedType[nestedTypeRootKey] = nestedTypeEndValue;
} else {
nestedType[nestedTypeRootKey] = fragmentedKeyList.reduce(function (collector, key, idx, list) {
var
partialType = collector.partialType || collector.type;
if (idx < (list.length - 1)) {
partialType[key] = {};
} else {
partialType[key] = collector.value;
}
collector.partialType = partialType[key];
return collector;
}, {
value : nestedTypeEndValue,
type : {}
}).type;
}
return collector;
}, {
source: type,
target: {}
}).target;
}
console.log('parseIntoNestedTypes :: type', JSON.stringify(obj));
console.log('parseIntoNestedTypes :: nestedType', JSON.stringify(parseIntoNestedTypes(obj)));
console.log('parseIntoNestedTypes :: type, nestedType : ', obj, parseIntoNestedTypes(obj));

Categories