Home > Software design >  Group array by nested array key, and duplicate items in result
Group array by nested array key, and duplicate items in result

Time:04-22

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

CodePudding user response:

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.

CodePudding user response:

I was getting a recursion error in my solution and had to go with making a deep copy of an object (JSON.parse(JSON.stringify(obj))) before pushing it into 2 different arrays. Also, considering your property might or might not be an array, I normalized that part with:

let keys = Array.isArray(obj[property]) ? obj[property] : [obj[property]];

let 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((acc, obj) => {
    let keys = Array.isArray(obj[property]) ? obj[property] : [obj[property]];
    keys.forEach(k => {
      acc[k] = acc[k] || [];
      acc[k].push(JSON.parse(JSON.stringify(obj)))
    })
    return acc;
  }, {});
}

console.log(groupBy(people, "departments"));
console.log(groupBy(people, "jobTitle"));

CodePudding user response:

I have only changed 3 lines of your snippet, converting everything to an array first, then looping through that array and creating groups makes the code easier. You could also check if it's an array and write similar code with an if statement.

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) {
    if (obj[property] === null || obj[property] === undefined) return acc; // if obj[property] is not defined or it's null don't add it to groupts
    if (obj[property].constructor !== Array) obj[property] = [obj[property]] // obj[property] is always an array now
    obj[property].forEach(key => {
      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);

  • Related