Reputation: 3666
I have a function used to flatten objects like so:
let object = {
a: 1,
b: [
{ c: 2 },
{ c: 3 }
]
};
flatten(object)
// returns {
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}
I need to unflatten objects, but also revert arrays to how they were. I have the following code:
unflatten(obj) {
let final = {};
for (let prop in obj) {
this.assign(final, prop.split('.'), obj[prop]);
}
return final;
}
assign(final, path, value) {
let lastKeyIndex = path.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
let key = path[i];
if (!(key in final)) {
final[key] = {};
}
final = final[key];
}
final[path[lastKeyIndex]] = value;
}
which works for the most part, but it treats arrays like so:
{
a: 1,
b: { // Notice how b's value is now an object
"0": { c: 2 }, // Notice how these now have a key corresponding to their index
"1": { c: 3 }
}
}
Whereas I need b
to be an array like before:
{
a: 1,
b: [
{ c: 2 },
{ c: 3 }
]
}
I'm at a loss for where to go from here. It needs to be able to deal with an arbitrary number of arrays like:
'a.b.0.c.0.d',
'a.b.0.c.1.d',
'a.b.1.c.0.d',
'a.b.1.c.1.d',
'a.b.1.c.2.d',
// etc
It needs to be vanilla JS, but es2015 is fine. It it assumed that any key that's a number is actually part of an array.
If anyone has any advice, it's appreciated!
Upvotes: 3
Views: 1690
Reputation: 16777
When you find that key
is not in final
, you should check to see if the next key in the path is only digits (using a regular expression) and, if so, assign to an array instead of an object:
if (!(key in final)) {
final[key] = /^\d+$/.test(path[i + 1]) ? [] : {};
}
let object = {
a: 1,
b: [{
c: 2
},
{
c: 3
}
]
};
let flattened = {
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}
function unflatten(obj) {
let final = {};
for (let prop in obj) {
assign(final, prop.split('.'), obj[prop]);
}
return final;
}
function assign (final, path, value) {
let lastKeyIndex = path.length - 1;
for (var i = 0; i < lastKeyIndex; ++i) {
let key = path[i];
if (!(key in final)) {
final[key] = /^\d+$/.test(path[i + 1]) ? [] : {};
}
final = final[key];
}
final[path[lastKeyIndex]] = value;
}
console.log(unflatten(flattened))
.as-console-wrapper { min-height: 100vh; }
Upvotes: 1
Reputation: 386756
You could iterate the keys and then split the string for single properties. For building a new object, you could check for number and take an array for these properties.
function setValue(object, path, value) {
var way = path.split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
function unFlatten(object) {
var keys = Object.keys(object),
result = isFinite(keys[0][0]) ? [] : {};
keys.forEach(function (k) {
setValue(result, k, object[k]);
});
return result;
}
console.log(unFlatten({
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}));
console.log(unFlatten({
'0': 1,
'1.0.c': 2,
'1.1.c': 3
}));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 0