Given a JSON like below
{
"main": {
"sub1": {
"cat": {
"subcat1":{
"count": 1
},
"subcat2":{
"count": 2
}
}
},
"sub2": {
"cat": {
"subcat1":{
"count": 3
},
"subcat2":{
"count": 5
}
}
}
}
}
Need to aggregate count at child level to its immediate parent till top-level parent like below
{
"main": {
"count": 11,
"sub1": {
"count": 3,
"cat": {
"count": 3,
"subcat1":{
"count": 1
},
"subcat2":{
"count": 2
}
}
},
"sub2": {
"count": 8,
"cat": {
"count": 8,
"subcat1":{
"count": 3
},
"subcat2":{
"count": 5
}
}
}
}
}
Tried to think of logic for the same, could not get to write anything. What would be right code/logic for same? One this is for sure, that i will nee some kind of recursion that keeps adding counts till parent level.
CodePudding user response:
You can write recount
using simple recursion. This solution depends on the leaf nodes (deepest nesting) to contain a numeric count
property -
function recount ({count, ...t}) {
if (count != null) {
return { ...t, count }
}
else {
const children =
Object.entries(t).map(([k, v]) => [k, recount(v)])
return {
count: children.reduce((r, [_, {count}]) => r count, 0),
...Object.fromEntries(children)
}
}
}
const myinput =
{main:{sub1:{cat:{subcat1:{count:1},subcat2:{count:2}}},sub2:{cat:{subcat1:{count:3},subcat2:{count:5}}}}}
console.log(recount(myinput))
{
"count": 11,
"main": {
"count": 11,
"sub1": {
"count": 3,
"cat": {
"count": 3,
"subcat1": {
"count": 1
},
"subcat2": {
"count": 2
}
}
},
"sub2": {
"count": 8,
"cat": {
"count": 8,
"subcat1": {
"count": 3
},
"subcat2": {
"count": 5
}
}
}
}
}
CodePudding user response:
const data = {
"main": {
"sub1": {
"cat": {
"subcat1":{
"count": 1
},
"subcat2":{
"count": 2
}
}
},
"sub2": {
"cat": {
"subcat1":{
"count": 3
},
"subcat2":{
"count": 5
}
}
}
}
};
const getCount = (thing) => {
if (!thing.count) {
thing.count = Object.values(thing).reduce((acc, el) => acc getCount(el), 0);
}
return thing.count;
}
const n = getCount(data);
console.log(n);
console.log(data);
CodePudding user response:
This is not very different from the answer from Mulan, but it shows the same process working with somewhat different syntax:
const sum = (ns) => ns .reduce ((a, b) => a b, 0)
const total = (
{count, ...rest},
kids = Object .entries (rest) .map (([k, v]) => [k, total (v)]),
kidsCount = sum (kids .map (([k, v]) => v .count))
) => count == undefined
? Object .fromEntries ([['count', kidsCount], ...kids])
: {count, ...rest}
const data = {main: {sub1: {cat: {subcat1: {count: 1}, subcat2: {count: 2}}}, sub2: {cat: {subcat1: {count: 3}, subcat2: {count: 5}}}}}
console .log (total (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
We pull out a count
property if it exists, then recur on the other properties of this object, leaving them in a useful form for Object .fromEntries
. From those, we extract and sum the count
properties, and then, if the count
already exists, we return a copy of the original object, and if not, we add one more entry to the kids
properties, and call Object .fromEntries
on that.
Note that we add a count
property to the root here, which wasn't in your requested output, but simply makes sense to me. If you don't want that, you can add a wrapper function, perhaps something like (data) => ({main: total (data .main || {})})
.
This uses some optional, defaulted parameters. There are times when that is a bad idea. If you want to avoid them, we can include them in an IIFE instead, like this:
const total = ({count, ...rest}) => ((
kids = Object .entries (rest) .map (([k, v]) => [k, total (v)]),
kidsCount = sum (kids .map (([k, v]) => v .count))
) => count == undefined
? Object .fromEntries ([['count', kidsCount], ...kids])
: {count, ...rest}
) ()
or we can just follow the technique from Mulan, where a local variable means you don't need them.