Reputation: 3471
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.
Upvotes: 30
Views: 17727
Reputation: 8087
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)
Upvotes: 2
Reputation: 2678
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);
Upvotes: 1
Reputation: 2691
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;
}
Upvotes: -1
Reputation: 38122
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'
}));
Upvotes: 48
Reputation: 386560
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);
Upvotes: 5
Reputation: 72865
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"
}
}
}
Upvotes: 3
Reputation: 3333
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:
Upvotes: 9
Reputation: 12742
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.
Upvotes: 1
Reputation: 140220
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"}}"
Upvotes: 5
Reputation: 23208
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);
Upvotes: 1