3

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'},
      ]
     }, 


 }

2
  • 1
    Hey @piotr.schaar. If I make an assumption that your initial data structure is an array of objects? (Which after being reduced looks as you have in the question). Then I can actually offer a solution to the whole problem and not just the second part. You can call groupBy(input, ["group", "Job"]) it it will return your desired output. You can also call this as nested as you like such as groupBy(input, ["group", "Job", "age"]) - stackoverflow.com/a/60741156/9792594 Commented Mar 19, 2020 at 8:21
  • 1
    I did have an error in the code, meaning it only worked up to 2 levels deep grouping but now it works for N levels deep :) stackoverflow.com/a/60741156/9792594 Commented Mar 19, 2020 at 15:41

6 Answers 6

3

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)

Sign up to request clarification or add additional context in comments.

Comments

2

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)

Comments

2

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);

Comments

1

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.

Comments

0

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)

Comments

0

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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.