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