I want to convert this array:
[{
department: 'HR',
person: 'Tom'
},{
department: 'Finance',
person: 'Peter'
},{
department: 'HR',
person: 'Jane'
}];
Into this one, grouping people by department
and changing the keys
[{
role: 'HR',
people: ['Tom','Jane']
},{
role: 'Finance',
people: ['Peter']
}]
I use this technique that have seem around and works really well, I am not really sure if it's got a name.
const data = [{department: 'HR', person: 'Tom'},{department: 'Finance',person: 'Peter'},{department: 'HR',person: 'Jane'}];
function groupPeopleByDepartmentWithObj(data) {
const obj = {};
for (const { department, person} of data) {
if (!obj[department]) {
obj[department] = { role: department, people: []};
}
obj[department].people.push(person);
}
return Object.values(obj);
}
console.log(groupPeopleByDepartmentWithObj(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using the ES6 Map object I can do the same with this
const data = [{department: 'HR', person: 'Tom'},{department: 'Finance',person: 'Peter'},{department: 'HR',person: 'Jane'}];
function groupPeopleByDepartmentWithMap(data) {
const mapper = new Map()
for (const { department, person} of data) {
if (!mapper.has(department)) {
mapper.set(department, { role: department, people: []})
}
mapper.get(department).people.push(person)
}
return Array.from(mapper.values())
}
console.log(groupPeopleByDepartmentWithMap(data))
.as-console-wrapper { max-height: 100% !important; top: 0; }
Is this approach better in any way, is there another way to use Map
to get the same result?
There's a fundamental difference between object properties and keys in a key-value store.
A property is something you define at the coding time, it has a fixed name and a well-defined meaning. A property is similar to a variable or a function.
A key is something that is only known at the run time, it is dynamic and has no inherent meaning except being associated with some value. A key is like an array index.
In the past, javascript Object
s were misused to emulate key-value stores, using property names as keys. This had several serious drawbacks
hasOwnProperty
etc)The Map
was invented to address these drawbacks specifically:
Map
keys can be anythingMap
keys are always in insertion orderTherefore, if you need a key-value store, Map
is always a better choice. Using generic Objects for this is a mistake.
As for the "better" way to use Maps for grouping, this is rather subjective. I'd prefer a more generic version
/// data: an Iterable
/// keyFn: a function which will be applied to each data item to obtain its group key
/// returns: a Map(key => [items])
// function groupBy<T, K>(data: Iterable<T>, keyFn: (x: T) => K): Map<K, T[]>
//
function groupBy(data, keyFn) {
let m = new Map();
for (let x of data) {
let k = keyFn(x);
if (!m.has(k))
m.set(k, []);
m.get(k).push(x);
}
return m;
}
which can be used like this for the task at hand:
let result = [];
for (let [role, items] of groupBy(data, x => x.department))
result.push({role, people: items.map(x => x.person)})