Object.groupBy e Map.groupBy: raggruppare un array senza reduce

Raggruppare gli elementi di un array per una chiave è una delle cose che si scrivono più spesso in JavaScript: ordini per cliente, righe per mese, prodotti per categoria. Per anni si è fatta con reduce, e il risultato era sempre lo stesso blocco di cinque righe che nessuno legge volentieri.
Da marzo 2024 non serve più. Object.groupBy e Map.groupBy sono entrati in ES2024 e oggi funzionano su tutti i browser aggiornati e su Node dalla versione 21.
Il codice che scrivevamo prima
const ordini = [
{ cliente: "Rossi", totale: 120, stato: "pagato" },
{ cliente: "Bianchi", totale: 80, stato: "in attesa" },
{ cliente: "Rossi", totale: 45, stato: "pagato" },
{ cliente: "Verdi", totale: 200, stato: "in attesa" },
];
const perStato = ordini.reduce((acc, o) => {
if (!acc[o.stato]) acc[o.stato] = [];
acc[o.stato].push(o);
return acc;
}, {});
Funziona, ma c’è molto rumore attorno a un’idea semplice. Bisogna inizializzare l’accumulatore, ricordarsi di creare l’array la prima volta che si incontra una chiave, e ricordarsi di ritornare acc alla fine — la dimenticanza più classica, quella che restituisce undefined is not an object al secondo giro.
Lo stesso codice oggi
const perStato = Object.groupBy(ordini, (o) => o.stato);
Il risultato è identico: un oggetto con una proprietà per ogni valore distinto, e dentro l’array degli elementi di quel gruppo.
{
pagato: [ {cliente: "Rossi", …}, {cliente: "Rossi", …} ],
"in attesa": [ {cliente: "Bianchi", …}, {cliente: "Verdi", …} ]
}
La callback riceve due argomenti, l’elemento e il suo indice, quindi si può raggruppare anche per posizione:
const perParità = Object.groupBy(ordini, (o, i) => (i % 2 ? "dispari" : "pari"));
E la chiave non deve per forza essere un campo già presente nei dati: può essere calcolata sul momento, ed è qui che il metodo si fa utile davvero.
const perFascia = Object.groupBy(ordini, (o) =>
o.totale >= 100 ? "sopra i 100" : "sotto i 100"
);
Due dettagli che conviene sapere
L’oggetto restituito ha prototipo null. Non è un oggetto normale: non eredita da Object.prototype. In pratica questo significa due cose. La prima è buona: se uno dei tuoi gruppi si chiama constructor o toString, non collide con niente, perché quelle proprietà non ci sono. La seconda è una trappola: su quell’oggetto non puoi chiamare perStato.hasOwnProperty("pagato"), perché il metodo non esiste. Si usa Object.hasOwn(perStato, "pagato"), oppure semplicemente "pagato" in perStato.
Le chiavi vengono convertite in stringa. La callback dovrebbe restituire una stringa o un symbol; qualsiasi altra cosa viene forzata a stringa. Se raggruppi per una data, Object.groupBy(righe, (r) => r.data) ti dà chiavi come "Tue Sep 01 2026 00:00:00 GMT+0100", che funzionano ma sono illeggibili. Meglio decidere tu il formato: (r) => r.data.toISOString().slice(0, 7) per raggruppare per mese.
Quando serve invece Map.groupBy
Map.groupBy ha la stessa identica firma e restituisce una Map al posto di un oggetto. La differenza sta tutta nelle chiavi: una Map accetta qualsiasi valore come chiave, non solo stringhe, e confronta con SameValueZero — cioè per identità, quando la chiave è un oggetto.
Serve quando il criterio di raggruppamento è un oggetto e non una sua descrizione testuale:
const clienteRossi = { id: 1, nome: "Rossi" };
const clienteVerdi = { id: 2, nome: "Verdi" };
const righe = [
{ cliente: clienteRossi, importo: 120 },
{ cliente: clienteVerdi, importo: 200 },
{ cliente: clienteRossi, importo: 45 },
];
const perCliente = Map.groupBy(righe, (r) => r.cliente);
perCliente.get(clienteRossi); // le due righe di Rossi
Da notare: perCliente.get({ id: 1, nome: "Rossi" }) non trova niente. La chiave è quell’oggetto lì, non uno uguale. Se ti serve poterla ricostruire, allora la chiave giusta è una stringa e il metodo giusto è Object.groupBy su r.cliente.id.
Nella pratica, nel codice di tutti i giorni Object.groupBy copre il novanta per cento dei casi. Map.groupBy torna utile quando hai già in mano i riferimenti agli oggetti — tipicamente perché li hai caricati una volta sola e li stai passando in giro — e vuoi evitare il giro stringa → lookup.
Su cosa gira
Entrambi i metodi sono disponibili sui browser da marzo 2024 e su Node.js da 21.0.0, che ha portato V8 11.8. Sono quindi utilizzabili senza pensieri in un progetto Node moderno e nel browser, se non devi supportare versioni di tre anni fa.
L’unico posto dove ancora vanno verificati è un build target vecchio: se il tuo bundler transpila verso ES2015 non li trasforma, perché non sono sintassi ma metodi — o c’è un polyfill, o non ci sono. In quel caso core-js li fornisce, oppure resta il reduce di prima, che male non fa.
In sintesi
Se scrivi ancora reduce per raggruppare, hai una riga sola al posto di cinque. Ricorda solo che l’oggetto che torna ha prototipo null — quindi Object.hasOwn al posto di hasOwnProperty — e che le chiavi diventano stringhe, quindi il formato lo decidi tu nella callback invece di lasciarlo decidere alla conversione automatica.
Fonti: Object.groupBy e Map.groupBy su MDN, changelog di Node.js 21.0.0.
