Search code examples
javascriptarraysgrouping

Group array by nested array key, and duplicate items in result


This is a bit tricky to find the right words, so hopefully showing some code will help.

I have the following (simplified) array of people and their departments. This comes from CMS data which allows a person to be added to multiple departments (hence why departments is an array).

[
  {
    id: '12345',
    name: 'Person 1',
    jobTitle: 'Engineering director',
    departments: ['Engineering', 'Leadership']
  },
  {
    id: '54321',
    name: 'Person 2',
    jobTitle: 'Junior engineer',
    departments: ['Engineering']
  },
  {
    id: '00001',
    name: 'Person 3',
    jobTitle: 'Founder',
    departments: ['Leadership']
  },
  {
    id: '00099',
    name: 'Person 4',
    jobTitle: 'No department',
    departments: []
  }
]

The result I'm after is to get the unique values of departments and create arrays for each, with the appropriate users inside it, so something like:

{
  'Engineering': [
    {
      id: '12345',
      name: 'Person 1',
      jobTitle: 'Engineering director',
      departments: ['Engineering', 'Leadership']
    },
    {
      id: '54321',
      name: 'Person 2',
      jobTitle: 'Junior engineer',
      departments: ['Engineering']
    }
  ],
  'Leadership': [
    {
      id: '12345',
      name: 'Person 1',
      jobTitle: 'Engineering director',
      departments: ['Engineering', 'Leadership']
    },
    {
      id: '00001',
      name: 'Person 3',
      jobTitle: 'Founder',
      departments: ['Leadership']
    }
  ]
}

I've got a groupBy function in my code already, but it doesn't quite do what I want (because it's not expecting an array as the property value), so if a person has multiple departments, I get an array with a concatenated name of both departments, but I want the same person to appear in multiple arrays instead.

This is my current groupBy function, but it's now distracting me and reduce is a concept my brain just really struggles with...!

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

// which I can use like: 

const groupedPeople = Object.entries(groupBy(people, "departments"));

// and it'll return
/*

{
  'Engineering,Leadership': [
    {
      id: '12345',
      name: 'Person 1',
      jobTitle: 'Engineering director',
      departments: ['Engineering', 'Leadership']
    }
  ],
  'Engineering': [
    {
      id: '54321',
      name: 'Person 2',
      jobTitle: 'Junior engineer',
      departments: ['Engineering']
    }
  ],
  'Leadership': [
    {
      id: '00001',
      name: 'Person 3',
      jobTitle: 'Founder',
      departments: ['Leadership']
    }
  ]
}

*/

I feel like I'm close, but can't get my brain to engage!


const people = [{
    id: '12345',
    name: 'Person 1',
    jobTitle: 'Engineering director',
    departments: ['Engineering', 'Leadership']
  },
  {
    id: '54321',
    name: 'Person 2',
    jobTitle: 'Junior engineer',
    departments: ['Engineering']
  },
  {
    id: '00001',
    name: 'Person 3',
    jobTitle: 'Founder',
    departments: ['Leadership']
  },
  {
    id: '00099',
    name: 'Person 4',
    jobTitle: 'No department',
    departments: []
  }
]

function groupBy(objectArray, property) {
  return objectArray.reduce(function(acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

// which I can use like: 

const groupedPeople = Object.entries(groupBy(people, "departments"));
console.log("Grouped:", groupedPeople);


Solution

  • var key = obj[property];
    

    on this line in your code, the key variable represents the array of deparments, which you then use as acc[key]. What JS does it that in converts the array into string to be used as a key of the acc object and the process for that is to just join the array by commas. What you need is to loop over the array instead:

    function groupBy(objectArray, property) {
      return objectArray.reduce(function (acc, obj) {
        var keys = obj[property];
        keys.forEach(key => {
          if (!acc[key]) {
            acc[key] = [];
          }
          acc[key].push(obj);
        })
        return acc;
      }, {});
    }
    

    Such change will make it work for your use case, the groupBy function will no longer work if the key is not an array, so use with caution or make it support both strings and arrays.