I would like to group by multiple keys such that a call like this:
groupBy(winners, ['country', 'athlete'])
On the following data:
[
{ athlete: "Michael Phelps", age: 19, country: "United States" },
{ athlete: "Michael Phelps", age: 27, country: "United States" },
{ athlete: "Kirsty Coventry", age: 24, country: "Zimbabwe" },
{ athlete: "Allison Schmitt", age: 22, country: "United States" },
]
Would produce (nested in order of key):
{
'United States': {
'Michael Phelps': [
{ athlete: "Michael Phelps", age: 19, country: "United States" },
{ athlete: "Michael Phelps", age: 27, country: "United States" }
],
'Allison Schmitt': [
{ athlete: "Allison Schmitt", age: 22 country: "United States" }
]
},
'Zimbabwe': {
'Kirsty Coventry': [
{ athlete: "Kirsty Coventry", age: 24, country: "Zimbabwe" }
]
}
}
Grouping by one key is easy, but I'm stuck on getting it to recursively group each group with the next key. This just groups by each key all in one level:
const get = (obj: Record<string, any>, k: string) =>
k.split(".").reduce((o, i) => (o ? o[i] : o), obj);
type GetValue<Item> = (item: Item) => string;
function groupBy<Item>(items: Item[], keys: (string | GetValue<Item>)[]) {
return keys.reduce(
(acc, key) => {
return items.reduce((accc, item) => {
const value =
typeof key === "function" ? key(item) : get(item, key);
(accc[value] = accc[value] || []).push(item);
return accc;
}, acc);
},
{} as Record<string, any>
);
}
const r = groupBy<Athlete>(winners.slice(0, 50), [
athlete => athlete.country,
"athlete"
]);
Here is a runnable example: https://codesandbox.io/s/groupby-ebgly?file=/src/index.ts:244-929
Apologies the extra complexity is around making it easy to specify keys using dot notation or a function for something even more complex such as a value in an array.
Thanks