Agrupando um array com groupBy - JavaScript
Agrupar dados em JavaScript costumava ser uma tarefa que exigia um pouco mais de esforço. Uma das abordagens mais comuns era usar o método reduce
. Recentemente, em julho de 2024, foi lançada no ES2024 (referência abaixo) a especificação para o método nativo groupBy
. Apesar de ser recente, atualmente a maioria dos navegadores já oferece suporte ao groupBy
, tornando essa tarefa (agrupar dados) muito mais simples.
Neste texto, abordaremos como agrupar dados usando tanto reduce
quanto groupBy
. Além disso, discutiremos seu suporte no TypeScript.
ECMAScript 2024, the 15th edition, added facilities for resizing and transferring ArrayBuffers and SharedArrayBuffers; added a new RegExp/v
flag for creating RegExps with more advanced features for working with sets of strings; and introduced thePromise.withResolvers
convenience method for constructing Promises, theObject.groupBy
andMap.groupBy
methods for aggregating data, theAtomics.waitAsync
method for asynchronously waiting for a change to shared memory, and theString.prototype.isWellFormed
andString.prototype.toWellFormed
methods for checking and ensuring that strings contain only well-formed Unicode.
Link: https://tc39.es/ecma262/
O que é o agrupamento de dados?
Imagine que você tem uma lista de produtos, cada um com suas características. O agrupamento de dados é como organizar essa lista em grupos menores, com base em características em comum. Por exemplo, você poderia agrupar todos os produtos da categoria "eletrônicos", ou todos os produtos com preço acima de R$100.
Agrupando dados com o reduce
Antes do suporte a função groupBy
, uma das alternativas era o uso do método reduce
para agrupar dados, que é uma função mais antiga, já com suporte amplo.
Se você nunca utilizou o método reduce
, pense nele como um liquidificador: você coloca diversos ingredientes e aperta o botão. No final, você tem um único produto: um suco. Basicamente, o reduce
"reduz" um array a um único valor. Ele faz isso aplicando uma função a cada elemento do array, acumulando o resultado a cada passo.
// ***********************************
// Exemplo de como somar valores
// utilizando o reduce
// ***********************************
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0); // O valor inicial é 0
console.log(sum); // Imprime 10
Devido a natureza de funcionamento do reduce
, podemos aproveitá-la para agrupar uma lista. Veja no exemplo abaixo o agrupamento de uma lista de transações por dia/mês:
const transacoes = [
{ data: new Date('2023-07-01T08:00:00.000-0300'),
valor: 150.5,
descricao: 'Pagamento de serviços' },
{ data: new Date('2023-07-01T12:00:00.000-0300'),
valor: -10,
descricao: 'Almoço' },
{ data: new Date('2023-07-05T12:00:00.000-0300'),
valor: -30.0,
descricao: 'Almoço com colegas' },
{ data: new Date('2023-07-05T19:00:00.000-0300'),
valor: -45.75,
descricao: 'Supermercado' },
{ data: new Date('2023-07-15T08:00:00.000-0300'),
valor: 1200.0,
descricao: 'Salário recebido' },
{ data: new Date('2023-07-20T10:00:00.000-0300'),
valor: -100.0,
descricao: 'Conta de luz' },
];
const transacoesAgrupadas = transacoes.reduce(
(dadosAgrupados, transacao) => {
const { data, valor, descricao } = transacao;
const dd = data.getDate().toString()
.padStart(2, '0');
const mm = data.getMonth().toString()
.padStart(2, '0');
const chave = `${dd}-${mm}`;
if (!dadosAgrupados.hasOwnProperty(chave)) {
dadosAgrupados[chave] = [];
}
dadosAgrupados[chave].push({ valor, descricao });
return dadosAgrupados;
},
{}
);
console.log(transacoesAgrupadas);
// ********************************
// Resultado:
// ********************************
const resultado = {
'01-06': [
{ descricao: 'Pagamento de serviços', valor: 150.5 },
{ descricao: 'Almoço', valor: -10 },
],
'05-06': [
{ descricao: 'Almoço com colegas', valor: -30 },
{ descricao: 'Supermercado', valor: -45.75 },
],
'15-06': [
{ descricao: 'Salário recebido', valor: 1200 },
],
'20-06': [
{ descricao: 'Conta de luz', valor: -100 },
],
};
Agrupando dados com o groupBy
A função groupBy
é bem recente e faz parte do ECMAScript 2024. Apesar de recente, a maioria dos navegadores já dá suporte a ela em suas versões mais atuais, inclusive o Node.js na versão 21.
As per usual a new version of the V8 engine is included in Node.js (updated to version 11.8, which is part of Chromium 118) bringing improved performance and new language features including:
Array groupingArrayBuffer.prototype.transfer
WebAssembly extended-const expressions
Link: https://nodejs.org/en/blog/announcements/v21-release-announce#v8-118
O uso do groupBy
é bem simples e intuitivo, veja no exemplo abaixo:
const transacoesAgrupadas = Object.groupBy(
// Lista que será agrupada:
transacoes,
// Função que retornará a "chave" de
// agrupamento para cada item. Neste
// exemplo a chave é a string "dia mês".
(transacao) => {
const { data } = transacao;
const dd = data.getDate().toString()
.padStart(2, "0");
const mm = data.getMonth().toString()
.padStart(2, "0");
return `${dd}-${mm}`;
}
);
Utilizando Polyfill para navegador antigos
Caso o local (runtime) onde o seu código será executado não dê suporte ao groupBy
, você poderá recorrer a uma polyfill. No contexto do JavaScript, polyfill é um pedaço de código que adiciona uma funcionalidade a um navegador ou ambiente de execução que não a suporta nativamente. Em outras palavras, é como se você estivesse "preenchendo" uma lacuna de funcionalidade.
Exemplo de Polyfill:
Object.groupBy ??= function groupBy (iterable, callbackfn) {
const obj = Object.create(null)
let i = 0
for (const value of iterable) {
const key = callbackfn(value, i++)
key in obj ? obj[key].push(value) : (obj[key] = [value])
}
return obj
}
Inclusão do groupBy
no TypeScript
Para projetos TypeScript, como o Angular, a versão 5.4 introduziu as declarações de tipo para os de métodos Object.groupBy
e Map.groupBy
do JavaScript.
Portanto, caso tenha um projeto TypeScript onde precise utilizar o groupBy
, para não pegar o erro abaixo, configure a lib
que fica no arquivo tsconfig.json para o ESNext
, já que na data corrente (julho/2024) não há a opção ES2024
:
Property 'groupBy' does not exist on type 'ObjectConstructor'.ts(2339)
lib
especifica quais bibliotecas de tipos (.d.ts
) devem ser incluídas no projeto. Isso determina quais tipos e interfaces estarão disponíveis para o projeto TypeScript. Por exemplo, se você incluir "dom" em lib
, o TypeScript terá conhecimento das interfaces do DOM (Document Object Model), como HTMLElement
, Event
, etc.Considerações Finais
Embora seja recente, acredito que a função groupBy
será amplamente utilizada. Eu mesmo já usei reduce
várias vezes para agrupar dados, e com o groupBy
, isso ficou muito mais simples.
Experimente implementar o groupBy
em seus projetos e veja como ele pode simplificar seu código e melhorar a eficiência do seu trabalho.
Links interessantes: