peon123
peon123

Reputation: 398

nested reduce function for objects

I am trying to reduce already reduced array and have problem with my object after second reduce function. My object before second function run looks like this:

const object = {
     groupA: [
       {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'},
       {name: "Eric", age: 25, Job: "Vet", group: 'groupA'},
       {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupA'},
       {name: "Peter", age: 25, Job: "Vet", group: 'groupA'},
     ],
    groupB: [
       {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'},
       {name: "Eric", age: 25, Job: "Vet", group: 'groupB'},
       {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupB'},
       {name: "Peter", age: 25, Job: "Vet", group: 'groupB'},
     ],    


 }

What function I should use if I want get results looking like this.


const object = {
     groupA: {
       pharmacist: [
         {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'},
         {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupA'},
      ],
      vet: [
       {name: "Eric", age: 25, Job: "Vet", group: 'groupA'},
       {name: "Peter", age: 25, Job: "Vet", group: 'groupA'},
      ]
     },
     groupB: {
       pharmacist: [
         {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'},
         {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupB'},
      ],
       vet: [
        {name: "Eric", age: 25, Job: "Vet", group: 'groupB'},
        {name: "Peter", age: 25, Job: "Vet", group: 'groupB'},
      ]
     }, 


 }

Upvotes: 3

Views: 959

Answers (6)

Alex L
Alex L

Reputation: 4241

We can do it like this (nested reduce):

const input = {
     groupA: [
       {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'},
       {name: "Eric", age: 25, Job: "Vet", group: 'groupA'},
       {name: "Bob", age: 26, Job: "Pharmacist", group: 'groupA'},
       {name: "Peter", age: 26, Job: "Vet", group: 'groupA'},
     ],
    groupB: [
       {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'},
       {name: "Eric", age: 25, Job: "Vet", group: 'groupB'},
       {name: "Bob", age: 26, Job: "Pharmacist", group: 'groupB'},
       {name: "Peter", age: 26, Job: "Vet", group: 'groupB'},
     ]
 }
 
 const output = (property) => {
  return Object.entries(input).reduce((aggObj, [key,val]) => {
    const grouped = val.reduce((groupedObj, item) => {
      if (groupedObj.hasOwnProperty(item[property])){
        groupedObj[item[property]].push(item);
      } else{
        groupedObj[item[property]] = [item];
      }          
      return groupedObj;
    }, {});        
    aggObj[key] = grouped;        
    return aggObj;
  }, {})
 }
 console.log(output('Job'));
 //we could also do the same for age:
 //console.log(output('age'));

/* Desired output: */
/*
const output = {
     groupA: {
       pharmacist: [
         {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'},
         {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupA'},
      ],
      vet: [
       {name: "Eric", age: 25, Job: "Vet", group: 'groupA'},
       {name: "Peter", age: 25, Job: "Vet", group: 'groupA'},
      ]
     },
     groupB: {
       pharmacist: [
         {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'},
         {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupB'},
      ],
       vet: [
        {name: "Eric", age: 25, Job: "Vet", group: 'groupB'},
        {name: "Peter", age: 25, Job: "Vet", group: 'groupB'},
      ]
     }, 


 }
 */

In essence, make an iterable from the Object with Object.entries(input)

Then iterate over these ([key, val]) entries and extract the key (i.e. first "GroupA" then "GroupB")

Each one of these values (val) is an array so we can reduce on it (i.e. val.reduce())

And then push each array that matches item.Job to the grouped object.

Then assign this to the final returned aggregated object and the end.

Update - recursive solution to groupBy up to N levels deep nested

I wanted to make it really generic and make an assumption about how your initial data looked and allow you do nested grouping like this on other properties (as nested as you want). That capability is displayed lower down in the answer (i.e. 3 levels deep grouping) but for now, here is your specific example:

You can call it simply like this groupBy(input, ["group", "Job"])

Where the result is grouped by group, then by Job, (nested).

for your specific example:

//assumed initial input, array of objects:
const input = [
  { name: "Adam", age: 25, Job: "Pharmacist", group: "groupA" },
  { name: "Eric", age: 25, Job: "Vet", group: "groupA" },
  { name: "Bob", age: 26, Job: "Pharmacist", group: "groupA" },
  { name: "Peter", age: 26, Job: "Vet", group: "groupA" },
  { name: "Adam", age: 25, Job: "Pharmacist", group: "groupB"},
  { name: "Eric", age: 25, Job: "Vet", group: "groupB" },
  { name: "Bob", age: 26, Job: "Pharmacist", group: "groupB" },
  { name: "Peter", age: 26, Job: "Vet", group: "groupB" }
];

const groupBy = (input, propertyArr) => {
  //console.log(propertyArr);
  const property = propertyArr[0];
  const grouped = input.reduce((groupedObj, item) => {
    groupedObj[item[property]] = [...(groupedObj[item[property]] || []), item];
    return groupedObj;
  }, {});
  if (propertyArr.length > 1) {
    //console.log(grouped);    
    return Object.keys(grouped).reduce((AggObj, key, index) => {
      const propertyArrCopy = [...propertyArr];
      propertyArrCopy.shift();
      AggObj[key] = groupBy(grouped[key], propertyArrCopy);
      return AggObj;
    }, {});
  } else {
    return grouped;
  }
};
console.log(groupBy(input, ["group", "Job"]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

With groupBy(input, ["group", "Job"])We get the output you are expecting:

{
  "groupA": {
    "Pharmacist": [
      {"name": "Adam", "age": 25, "Job": "Pharmacist", "group": "groupA"},
      {"name": "Bob", "age": 26, "Job": "Pharmacist", "group": "groupA"}
    ],
    "Vet": [
      {"name": "Eric", "age": 25, "Job": "Vet", "group": "groupA"},
      {"name": "Peter", "age": 26, "Job": "Vet", "group": "groupA"}
    ]
  },
  "groupB": {
    "Pharmacist": [
      {"name": "Adam", "age": 25, "Job": "Pharmacist", "group": "groupB"},
      {"name": "Bob", "age": 26, "Job": "Pharmacist", "group": "groupB"}
    ],
    "Vet": [
      {"name": "Eric", "age": 25, "Job": "Vet", "group": "groupB"},
      {"name": "Peter", "age": 26, "Job": "Vet", "group": "groupB"}
    ]
  }
}

Example of the capability of the more general solution with 3 levels deep grouping:

i.e. calling groupBy(input, ["group", "Job", "age"])

Where the result is grouped by group, then by Job, then by age (nested).

//assumed initial input, array of objects:
const input = [
  { name: "Adam", age: 25, Job: "Pharmacist", group: "groupA" },
  { name: "Lauren", age: 25, Job: "Pharmacist", group: "groupA" },
  { name: "Eric", age: 25, Job: "Vet", group: "groupA" },
  { name: "Theresa", age: 25, Job: "Vet", group: "groupA" },
  { name: "Bob", age: 26, Job: "Pharmacist", group: "groupA" },
  { name: "Brandy", age: 26, Job: "Pharmacist", group: "groupA" },
  { name: "Alex", age: 26, Job: "Scientist", group: "groupA" },
  { name: "Tom", age: 26, Job: "Scientist", group: "groupA" },
  { name: "Peter", age: 26, Job: "Vet", group: "groupA" },
  { name: "Kate", age: 26, Job: "Vet", group: "groupA" },
  { name: "Adam", age: 25, Job: "Pharmacist", group: "groupB" },
  { name: "Sarah", age: 25, Job: "Pharmacist", group: "groupB" },
  { name: "Eric", age: 25, Job: "Vet", group: "groupB" },
  { name: "Sophie", age: 25, Job: "Vet", group: "groupB" },
  { name: "Bob", age: 26, Job: "Pharmacist", group: "groupB" },
  { name: "Anne", age: 26, Job: "Pharmacist", group: "groupB" },
  { name: "Peter", age: 26, Job: "Vet", group: "groupB" },
  { name: "Mary", age: 26, Job: "Vet", group: "groupB" },
  { name: "Alex", age: 26, Job: "Scientist", group: "groupB" },
  { name: "Sarah", age: 26, Job: "Scientist", group: "groupB" }
];

const groupBy = (input, propertyArr) => {
  //console.log(propertyArr);
  const property = propertyArr[0];
  const grouped = input.reduce((groupedObj, item) => {
    groupedObj[item[property]] = [...(groupedObj[item[property]] || []), item];
    return groupedObj;
  }, {});
  if (propertyArr.length > 1) {
    //console.log(grouped);    
    return Object.keys(grouped).reduce((AggObj, key, index) => {
      const propertyArrCopy = [...propertyArr];
      propertyArrCopy.shift();
      AggObj[key] = groupBy(grouped[key], propertyArrCopy);
      return AggObj;
    }, {});
  } else {
    return grouped;
  }
};
console.log(groupBy(input, ["group", "Job", "age"]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

I added some more objects into the initial array to make it a bit more interesting. This can also be extended for much larger data sets and with many more levels of nesting and should work fine.

Upvotes: 1

Siva Kondapi Venkata
Siva Kondapi Venkata

Reputation: 11011

Write function arrToObj to convert an array into Object with grouped keys.
Apply the above method to all entries in object and build a new Object.

const object = {
  groupA: [
    { name: "Adam", age: 25, Job: "Pharmacist", group: "groupA" },
    { name: "Eric", age: 25, Job: "Vet", group: "groupA" },
    { name: "Bob", age: 25, Job: "Pharmacist", group: "groupA" },
    { name: "Peter", age: 25, Job: "Vet", group: "groupA" }
  ],
  groupB: [
    { name: "Adam", age: 25, Job: "Pharmacist", group: "groupB" },
    { name: "Eric", age: 25, Job: "Vet", group: "groupB" },
    { name: "Bob", age: 25, Job: "Pharmacist", group: "groupB" },
    { name: "Peter", age: 25, Job: "Vet", group: "groupB" }
  ]
};

const arrToObj = arr => {
  const res = {};
  arr.forEach(item => {
    if (!res[item.Job]) {
      res[item.Job] = [];
    }
    res[item.Job].push(item);
  });
  return res;
};

const newObject = Object.fromEntries(
  Object.entries(object).map(([key, value]) => [key, arrToObj(value)])
);

console.log(newObject);

Upvotes: 0

Jonas Tomanga
Jonas Tomanga

Reputation: 1108

You can use filter and reduce

const object = {
     groupA: [
       {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'},
       {name: "Eric", age: 25, Job: "Vet", group: 'groupA'},
       {name: "Name", age: 25, Job: "Pharmacist", group: 'groupA'},
       {name: "Name", age: 25, Job: "Vet", group: 'groupA'},
     ],
    groupB: [
       {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'},
       {name: "Eric", age: 25, Job: "Vet", group: 'groupB'},
       {name: "Name", age: 25, Job: "Pharmacist", group: 'groupB'},
       {name: "Name", age: 25, Job: "Vet", group: 'groupB'},
     ],    
 }
 
 for(const key in object){
   object[key] = object[key]
   .map(a => a.Job)
   .filter((v,i,c)=> c.indexOf(v) === i)
   .reduce((acc, i) => ({...acc, [i]: object[key].filter(p => p.Job === i)}), {})
 }
 
 console.log(object)

Upvotes: 0

Nick Parsons
Nick Parsons

Reputation: 50954

You could grab the entries of your object by using Object.entries() and then .map() every value-array to a grouped object based on the key Job. To group the array-values, you can use .reduce() by accumulating an object with array-values containing the objects for each associated Job. You can then use Object.fromEntries() to build your resulting object from the mapped entries:

const object = { groupA: [ {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'}, {name: "Eric", age: 25, Job: "Vet", group: 'groupA'}, {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupA'}, {name: "Peter", age: 25, Job: "Vet", group: 'groupA'}, ], groupB: [ {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'}, {name: "Eric", age: 25, Job: "Vet", group: 'groupB'}, {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupB'}, {name: "Peter", age: 25, Job: "Vet", group: 'groupB'}, ], }
 
const res = Object.fromEntries(Object.entries(object).map(
  ([key, arr]) => [key, arr.reduce((acc, obj) => {
    acc[obj.Job] = [...(acc[obj.Job] || []), obj];
    return acc;
  }, {})]
));

console.log(res);

If you can't support Object.fromEntries() you could .map() to objects instead of [key, value] pair arrays, and then spread the results into Object.assign() like so:

const object = { groupA: [ {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupA'}, {name: "Eric", age: 25, Job: "Vet", group: 'groupA'}, {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupA'}, {name: "Peter", age: 25, Job: "Vet", group: 'groupA'}, ], groupB: [ {name: "Adam", age: 25, Job: "Pharmacist", group: 'groupB'}, {name: "Eric", age: 25, Job: "Vet", group: 'groupB'}, {name: "Bob", age: 25, Job: "Pharmacist", group: 'groupB'}, {name: "Peter", age: 25, Job: "Vet", group: 'groupB'}, ], }
 
const res = Object.assign({}, ...Object.entries(object).map(
  ([key, arr]) => ({[key]: arr.reduce((acc, obj) => {
    acc[obj.Job] = [...(acc[obj.Job] || []), obj];
    return acc;
  }, {})})
));

console.log(res);

Upvotes: 2

Maheer Ali
Maheer Ali

Reputation: 36594

You can create a separate function to reduce() each group and then use for..in to replace unreduced array of each group with a reduced object

const object = {"groupA":[{"name":"Adam","age":25,"Job":"Pharmacist","group":"groupA"},{"name":"Eric","age":25,"Job":"Vet","group":"groupA"},{"name":"","age":25,"Job":"Pharmacist","group":"groupA"},{"name":"","age":25,"Job":"Vet","group":"groupA"}],"groupB":[{"name":"Adam","age":25,"Job":"Pharmacist","group":"groupB"},{"name":"Eric","age":25,"Job":"Vet","group":"groupB"},{"name":"","age":25,"Job":"Pharmacist","group":"groupB"},{"name":"","age":25,"Job":"Vet","group":"groupB"}]}

function groupByJob(arr){
  return arr.reduce((ac, a) => {
    if(!ac[a.Job]){
      ac[a.Job] = [];
    }
    ac[a.Job].push(a);
    return ac;
  }, {});
}

for(let k in object){
  object[k] = groupByJob(object[k]);
}
console.log(object)

If you don't want splitting you can directly apply map() on entries of object and then use map() directly on on each value.

const object = {"groupA":[{"name":"Adam","age":25,"Job":"Pharmacist","group":"groupA"},{"name":"Eric","age":25,"Job":"Vet","group":"groupA"},{"name":"","age":25,"Job":"Pharmacist","group":"groupA"},{"name":"","age":25,"Job":"Vet","group":"groupA"}],"groupB":[{"name":"Adam","age":25,"Job":"Pharmacist","group":"groupB"},{"name":"Eric","age":25,"Job":"Vet","group":"groupB"},{"name":"","age":25,"Job":"Pharmacist","group":"groupB"},{"name":"","age":25,"Job":"Vet","group":"groupB"}]}

const res = Object.fromEntries(
               Object.entries(object)
                 .map(([k, v]) => 
                    [  
                       k, 
                       v.reduce((ac, a) => 
                           (ac[a.Job] = (ac[a.Job] || []).concat(a), ac), 
                       {})
                    ]
                  )
                )

console.log(res)

Upvotes: 3

Nenad Vracar
Nenad Vracar

Reputation: 122125

You can use nested reduce method to create new object with grouped values by Job.

const object = {"groupA":[{"name":"Adam","age":25,"Job":"Pharmacist","group":"groupA"},{"name":"Eric","age":25,"Job":"Vet","group":"groupA"},{"name":"","age":25,"Job":"Pharmacist","group":"groupA"},{"name":"","age":25,"Job":"Vet","group":"groupA"}],"groupB":[{"name":"Adam","age":25,"Job":"Pharmacist","group":"groupB"},{"name":"Eric","age":25,"Job":"Vet","group":"groupB"},{"name":"","age":25,"Job":"Pharmacist","group":"groupB"},{"name":"","age":25,"Job":"Vet","group":"groupB"}]}

const result = Object.entries(object).reduce((r, [k, v]) => {
  r[k] = v.reduce((r, e) => {
    if (!r[e.Job]) r[e.Job] = [e]
    else r[e.Job].push(e)
    return r
  }, {})

  return r;
}, {})


console.log(result)

Upvotes: 2

Related Questions