devserkan
devserkan

Reputation: 17638

Using nested reduces on an object by properties

I have data like this:

const data = [
  { id: 1, cat: "human", age: "10" },
  { id: 2, cat: "human", age: "20" },
  { id: 3, cat: "human", age: "10" },
  { id: 4, cat: "animal", age: "10" },
  { id: 5, cat: "animal", age: "20" },
  { id: 6, cat: "animal", age: "10" },
  { id: 7, cat: "alien", age: "10" },
  { id: 8, cat: "alien", age: "20" },
  { id: 9, cat: "alien", age: "10" },
];

I want to group this data something like that:

const gr = {
  human: {
    all: [
      { id: 1, cat: "human", age: "10" },
      { id: 2, cat: "human", age: "20" },
      { id: 3, cat: "human", age: "10" },
    ],
    ages: {
      "10": [
        { id: 1, cat: "human", age: "10" },
        { id: 3, cat: "human", age: "10" },
      ],
      "20": [
        { id: 2, cat: "human", age: "20" },
      ],
    }
  },
  animal: {...},
  alien: {...},
}

I do first reduce like that:

const gr = data.reduce((acc, el) => {
  const { cat } = el;
  acc[cat] = acc[cat] || { all: [] };
  acc[cat].all.push(el);

  return acc;
}, {});

But I can't make a nested reduce here. I can do it separately like that:

const grAge = gr.human.all.reduce((acc,el) => {
    const {age} = el;
    acc[age] = acc[age] || [];
    acc[age].push(el);

    return acc;
  },{});

gr.human["ages"] = grAge;

But obviously, this is not so efficient and needs more work. Maybe like this:

Object.keys(gr).forEach(key => {
  const grAge = gr[key].all.reduce((acc,el) => {
    const {age} = el;
    acc[age] = acc[age] || [];
    acc[age].push(el);

    return acc;
  },{});

  gr[key]["ages"] = grAge;
});

Can I join those reduces in a single step?

If there are any other good methods I can use them, I don't need to use the reduce method.

Upvotes: 3

Views: 73

Answers (2)

Treycos
Treycos

Reputation: 7492

Another way to do this would be to get every unique category and ages using Sets, and then reducing them into your final JSON :

EDIT : It seems like the Stack Overflow snippet doesn't like it, but executing it in your browser console will give out the correct result

const data = [
	{ id: 1, cat: "human", age: "10" },
	{ id: 2, cat: "human", age: "20" },
	{ id: 3, cat: "human", age: "10" },
	{ id: 4, cat: "animal", age: "10" },
	{ id: 5, cat: "animal", age: "20" },
	{ id: 6, cat: "animal", age: "10" },
	{ id: 7, cat: "alien", age: "10" },
	{ id: 8, cat: "alien", age: "20" },
	{ id: 9, cat: "alien", age: "10" },
];

const output = [...new Set(data.map(thing => thing.cat))].reduce((acc, category) => {
	const catData = data.filter(thing => thing.cat === category)

	return {
		[category]: {
			all: catData,
			ages : [...new Set(catData.map(catThing => catThing.age))].reduce((catAcc, age) => ({
				[age]: [...catData.filter(catThing => catThing.age === age)],
				...catAcc
			}), {})
		},
		...acc
	}
}, {})

console.log(output)
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386756

You could take a sinle loop approach and assign the wanted structure to either allor to a nested strcture.

If you like to get a more dynamic version, you need to simplify the result structure for every nesting level (this means, the age level would contain an all property).

const
    data = [{ id: 1, cat: "human", age: "10" }, { id: 2, cat: "human", age: "20" }, { id: 3, cat: "human", age: "10" }, { id: 4, cat: "animal", age: "10" }, { id: 5, cat: "animal", age: "20" }, { id: 6, cat: "animal", age: "10" }, { id: 7, cat: "alien", age: "10" }, { id: 8, cat: "alien", age: "20" }, { id: 9, cat: "alien", age: "10" }],
    result = data.reduce((r, o) => {
        r[o.cat] = r[o.cat] || { all: [], ages: {} };        
        r[o.cat].all.push(o);
        r[o.cat].ages[o.age] = r[o.cat].ages[o.age] || [];
        r[o.cat].ages[o.age].push(o);
        return r;
    }, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 3

Related Questions