The hole is a little lower. Note that you want to "flatten" an object that can contain, in the various nesting levels, repeated keys. And as I explained here, in Javascript, an object nay can own two properties with the same key.
I mean, that’s exactly what you assumed:
Just that I think, like inside ganhou
and perdeu
have the same names contar
and porcentagem
, he ignores [...]
Your code performs "flattening" correctly. The problem is that it does not create distinct names for each nested property. That is, the same key can be used several times (as it is actually happening). And, in this case, there is a loss data, since the properties are silently overwritten, so only the latter will prevail.
An option to resolve this is to pass the name of the property current as the function’s third argument flatten
:
function flatten(obj, final = {}, baseKey = '') {
for (const key of Object.keys(obj)) {
const givenKey = !baseKey ? key : baseKey + ucfirst(key);
if (typeof obj[key] === 'object' && obj[key] !== null) {
flatten(obj[key], final, givenKey);
} else {
final[givenKey] = obj[key];
}
}
return final;
}
function ucfirst(str) {
return str[0].toUpperCase() + str.slice(1);
}
const original = {
id: 1,
maxiTempo: 30,
todos: {
contar: 2,
porcentagem: 1
},
perdeu: {
contar: 4,
porcentagem: 1.3
},
ganhou: {
contar: 26,
porcentagem: 0.9,
testNest: {
ok: true
}
}
};
console.log(flatten(original));
Note that I created a function ucfirst
to capitalize the names correctly. Note also that I preferred not to use for..in
(in favor of Object.keys
- one of several alternatives) to avoid the potentially undesirable situation of reading an enumerable property resulting from careless definition in the prototypical chain.
Out of personal curiosity, I ended up doing another alternative eliminating recursion, which can be problematic because most Javascript implementations do not optimize tail recursion and stack the calls until an eventual catastrophic burst.
function flatten(obj) {
const final = {};
const queue = [['', obj]];
while (queue.length) {
const [baseKey, currentObj] = queue.pop();
for (const [key, val] of Object.entries(currentObj)) {
const givenKey = !baseKey ? key : baseKey + ucfirst(key);
if (isObject(val)) {
queue.push([givenKey, val]);
} else {
final[givenKey] = val;
}
}
}
return final;
}
function ucfirst(str) {
return str[0].toUpperCase() + str.slice(1);
}
function isObject(val) {
return typeof val === 'object' && val !== null;
}
const original = {
id: 1,
maxiTempo: 30,
todos: {
contar: 2,
porcentagem: 1
},
perdeu: {
contar: 4,
porcentagem: 1.3
},
ganhou: {
contar: 26,
porcentagem: 0.9,
testNest: {
ok: true
}
}
};
console.log(flatten(original));
And to transform each element of the array, just perform a mapping. You can use one for
calling the function flatten
or use its own Array.prototype.map
.
It is also worth noting that the two codes above can’t stand it cyclic structures of objects.
Damn, I understood what you explained. Excellent answer, thank you!!! I just wanted to ask one more question if you allow me. If I pass my object with only one item in its function, it will "flatten" correctly, as you showed but what if it is an array of objects? it will flatten everything into a single item? I will edit my example to explain better.
– Isa
@Isa, you’re welcome! I made an observation about that at the end of the answer. One way to do this is to iterate over each element of the array and do the operation individually. Perhaps the simplest way to do that is to use the
map
, thus:const myNewArray = myArray.map((obj) => flatten(obj));
.– Luiz Felipe
really helped me a lot!!!
– Isa
Excellent response!!!!!
– Isa