Array de objetos em JavaScript: filtrar e somar
Receitas de array de objetos: somar com reduce, filtrar por propriedade, agrupar com Object.groupBy e achar duplicado — tudo executado no Node 24.
Um array de objetos é uma lista em que cada posição guarda um registro completo, com várias propriedades relacionadas. É o formato comum de uma resposta de API, das linhas de um pedido ou de um carrinho salvo no navegador.
Pense numa planilha de pedidos: o array é o conjunto de linhas; cada objeto é
uma linha; e propriedades como sku, preco e quantidade são as colunas.
Filtrar escolhe algumas linhas, ordenar muda a sequência, agrupar separa as
linhas por uma coluna e reduzir transforma o conjunto em um resultado, como o
total da compra. No JavaScript não existe uma planilha por trás: existem objetos
guardados nos índices do array, e cada método percorre essas referências.
Cinco operações cobrem boa parte do trabalho: somar um campo com reduce,
filtrar por propriedade com filter, ordenar por chave com toSorted, agrupar
com Object.groupBy e indexar por identificador com Map. Vamos usar o mesmo
pedido de uma loja de periféricos para enxergar o que entra e o que sai de cada
etapa — inclusive quando um item procurado não existe.
A lista de itens de um pedido
const itens = [
{ sku: 'TEC-001', nome: 'Teclado mecânico', preco: 289.9, quantidade: 1, categoria: 'periferico' },
{ sku: 'MOU-014', nome: 'Mouse sem fio', preco: 149.9, quantidade: 2, categoria: 'periferico' },
{ sku: 'CAD-220', nome: 'Cadeira gamer', preco: 1199, quantidade: 1, categoria: 'movel' },
{ sku: 'MON-007', nome: 'Monitor 27 polegadas', preco: 1899.9, quantidade: 1, categoria: 'monitor' },
];
console.log(itens.length);
console.log(itens[1].nome);
console.log(itens.at(-1).nome);Cada posição do array guarda um objeto inteiro, e o acesso é sempre em dois
passos: primeiro a posição, depois a propriedade. at(-1) pega o último sem
precisar escrever itens[itens.length - 1].
Somar um campo com reduce
reduce percorre a lista carregando um acumulador. O segundo argumento — o
valor inicial — é o que decide se a conta funciona:
const itens = [
{ sku: 'TEC-001', preco: 289.9, quantidade: 1 },
{ sku: 'MOU-014', preco: 149.9, quantidade: 2 },
{ sku: 'CAD-220', preco: 1199, quantidade: 1 },
];
const total = itens.reduce((soma, item) => soma + item.preco * item.quantidade, 0);
console.log(total);
console.log(total.toFixed(2));Sem o 0, o acumulador começa valendo o primeiro objeto da lista, e a soma
vira concatenação de texto:
const itens = [
{ sku: 'TEC-001', preco: 289.9 },
{ sku: 'MOU-014', preco: 149.9 },
{ sku: 'CAD-220', preco: 1199 },
];
const total = itens.reduce((soma, item) => soma + item.preco);
console.log(total);Nenhum erro, nenhum aviso: só um total que não é número. Esse é o tipo de bug
que chega ao cliente. Em array de objetos, o valor inicial do reduce nunca
é opcional.
Filtrar por propriedade e somar o recorte
filter devolve um array novo com os itens que passaram no teste, e ele
encadeia direto com reduce e map:
const itens = [
{ nome: 'Teclado mecânico', preco: 289.9, categoria: 'periferico' },
{ nome: 'Mouse sem fio', preco: 149.9, categoria: 'periferico' },
{ nome: 'Cadeira gamer', preco: 1199, categoria: 'movel' },
{ nome: 'Monitor 27 polegadas', preco: 1899.9, categoria: 'monitor' },
];
const perifericos = itens.filter((i) => i.categoria === 'periferico');
console.log(perifericos.length, perifericos.map((i) => i.nome));
const totalPerifericos = perifericos.reduce((s, i) => s + i.preco, 0);
console.log(totalPerifericos.toFixed(2));
const acimaDe1000 = itens.filter((i) => i.preco > 1000).map((i) => i.nome);
console.log(acimaDe1000);Cada método da cadeia cria um array intermediário. Em lista de pedido isso é irrelevante; em lista com dezenas de milhares de linhas, vale medir antes de encadear cinco operações.
Ordenar por um campo calculado
Ordenar por uma propriedade que já existe é assunto de ordenar array com sort. O caso próprio de array de objetos é outro: ordenar por um número que não está no objeto e precisa ser calculado antes — o subtotal da linha, por exemplo.
const itens = [
{ sku: 'TEC-001', nome: 'Teclado mecânico', preco: 289.9, quantidade: 1 },
{ sku: 'MOU-014', nome: 'Mouse sem fio', preco: 149.9, quantidade: 2 },
{ sku: 'CAD-220', nome: 'Cadeira gamer', preco: 1199, quantidade: 1 },
{ sku: 'MON-007', nome: 'Monitor 27 polegadas', preco: 1899.9, quantidade: 1 },
];
const porSubtotal = itens
.map((i) => ({ ...i, subtotal: i.preco * i.quantidade }))
.toSorted((a, b) => b.subtotal - a.subtotal);
console.log(porSubtotal.map((i) => `${i.nome}: ${i.subtotal.toFixed(2)}`));
console.log(porSubtotal.slice(0, 2).map((i) => i.sku));
console.log(itens[0].subtotal);O map com spread cria objetos novos com o campo a mais, em vez de escrever
subtotal dentro dos itens originais — por isso a última linha imprime
undefined. É o detalhe que separa este encadeamento de um laço que suja os
dados de entrada: a lista que chegou continua exatamente como chegou, e o
slice(0, 2) entrega o pódio sem que ninguém precise confiar na ordem da API.
Repare também que o mouse subiu na frente do teclado por causa da quantidade:
149,90 sozinho perde para 289,90, mas duas unidades somam 299,80. Ordenar pelo
preco cru daria outro ranking — e o errado, se a pergunta era “o que mais
pesou nesse pedido?”.
Agrupar por categoria
Object.groupBy recebe a lista e uma função que devolve a chave de cada item:
const itens = [
{ nome: 'Teclado mecânico', preco: 289.9, categoria: 'periferico' },
{ nome: 'Mouse sem fio', preco: 149.9, categoria: 'periferico' },
{ nome: 'Cadeira gamer', preco: 1199, categoria: 'movel' },
{ nome: 'Monitor 27 polegadas', preco: 1899.9, categoria: 'monitor' },
];
const porCategoria = Object.groupBy(itens, (i) => i.categoria);
console.log(Object.keys(porCategoria));
console.log(porCategoria.periferico.map((i) => i.nome));
console.log(porCategoria.eletrodomestico);Categoria que não aparece na lista não vira chave — daí o undefined da última
linha, que precisa de guarda antes do .map. Uma particularidade do
groupBy: o objeto devolvido não herda de Object.prototype.
const itens = [
{ nome: 'Teclado mecânico', categoria: 'periferico' },
{ nome: 'Cadeira gamer', categoria: 'movel' },
];
const porCategoria = Object.groupBy(itens, (i) => i.categoria);
console.log(porCategoria);
console.log(porCategoria.hasOwnProperty);O [Object: null prototype] na saída não é erro: é proteção. Sem protótipo,
uma categoria chamada constructor ou toString não colide com método
herdado. O preço é que porCategoria.hasOwnProperty(...) não existe — use
Object.hasOwn(porCategoria, 'movel').
Total por categoria com reduce
Quando o que você quer não são as listas, e sim o número somado, o reduce com
acumulador em objeto resolve:
const itens = [
{ nome: 'Teclado mecânico', preco: 289.9, categoria: 'periferico' },
{ nome: 'Mouse sem fio', preco: 149.9, categoria: 'periferico' },
{ nome: 'Cadeira gamer', preco: 1199, categoria: 'movel' },
{ nome: 'Monitor 27 polegadas', preco: 1899.9, categoria: 'monitor' },
];
const totalPorCategoria = itens.reduce((acc, item) => {
acc[item.categoria] = (acc[item.categoria] ?? 0) + item.preco;
return acc;
}, {});
console.log(totalPorCategoria);O ?? 0 cobre a primeira vez que cada categoria aparece, quando ainda não
existe chave. E o return acc é obrigatório: esquecer dele faz o acumulador
virar undefined na segunda volta. Para percorrer o resultado depois, veja
percorrer objeto em JavaScript.
Achar duplicado
O SKU repetido no carrinho é o caso clássico:
const itens = [
{ sku: 'TEC-001', nome: 'Teclado mecânico' },
{ sku: 'MOU-014', nome: 'Mouse sem fio' },
{ sku: 'TEC-001', nome: 'Teclado mecânico' },
{ sku: 'CAD-220', nome: 'Cadeira gamer' },
];
const skus = itens.map((i) => i.sku);
const unicos = new Set(skus);
console.log(skus.length, unicos.size);
console.log(skus.length !== unicos.size);
const duplicados = skus.filter((sku, i) => skus.indexOf(sku) !== i);
console.log(duplicados);O truque do filter com indexOf funciona porque indexOf sempre devolve a
primeira posição do valor: quando ela é diferente do índice atual, aquele
item é uma repetição.
Objeto igual não entra no includes
Nunca tente localizar um objeto pelo conteúdo com includes:
const itens = [
{ sku: 'TEC-001', nome: 'Teclado mecânico' },
{ sku: 'MOU-014', nome: 'Mouse sem fio' },
];
const procurado = { sku: 'TEC-001', nome: 'Teclado mecânico' };
console.log(itens.includes(procurado));
console.log(itens[0] === procurado);
console.log(JSON.stringify(itens[0]) === JSON.stringify(procurado));
console.log(itens.some((i) => i.sku === procurado.sku));Conteúdo idêntico, referências diferentes. O JSON.stringify prova que os
dados batem, mas não serve como comparação de verdade — ele depende da ordem
das chaves e engasga com undefined, Date e valores circulares. A comparação
honesta é por campo identificador, com some, como detalha
buscar elemento em array.
Índice por SKU com Map
Quando você vai procurar muitas vezes na mesma lista, transformar o array num
Map uma vez sai mais barato do que rodar find a cada consulta:
const catalogo = [
{ sku: 'TEC-001', nome: 'Teclado mecânico', preco: 289.9 },
{ sku: 'MOU-014', nome: 'Mouse sem fio', preco: 149.9 },
{ sku: 'CAD-220', nome: 'Cadeira gamer', preco: 1199 },
];
const porSku = new Map(catalogo.map((p) => [p.sku, p]));
console.log(porSku.get('CAD-220'));
console.log(porSku.has('MON-007'));
console.log(porSku.size);new Map recebe uma lista de pares [chave, valor] — exatamente o formato que
o map produziu. O Map também é a forma correta de eliminar duplicados
mantendo o objeto: [...new Map(itens.map((i) => [i.sku, i])).values()].
Maior, menor e média
const itens = [
{ nome: 'Teclado mecânico', preco: 289.9 },
{ nome: 'Mouse sem fio', preco: 149.9 },
{ nome: 'Cadeira gamer', preco: 1199 },
];
const maisCaro = itens.reduce((maior, i) => (i.preco > maior.preco ? i : maior));
const media = itens.reduce((s, i) => s + i.preco, 0) / itens.length;
console.log(maisCaro.nome);
console.log(media.toFixed(2));
console.log(Math.min(...itens.map((i) => i.preco)));Aqui o reduce aparece sem valor inicial de propósito: o acumulador começa
sendo o primeiro item, que é o candidato natural a “maior”. O preço disso é que
lista vazia quebra:
const carrinhoVazio = [];
console.log(carrinhoVazio.reduce((s, i) => s + i.preco, 0));
const maisCaro = carrinhoVazio.reduce((maior, i) => (i.preco > maior.preco ? i : maior));
console.log(maisCaro);A soma com 0 passou tranquila; a busca do maior, sem inicial, não teve de
onde começar. Carrinho vazio é estado normal de e-commerce, não caso raro.
Erros comuns: undefined dentro do reduce
O erro mais frequente com array de objetos não nasce no reduce — nasce no
map que veio antes dele:
const catalogo = [
{ sku: 'TEC-001', nome: 'Teclado mecânico', preco: 289.9 },
{ sku: 'MOU-014', nome: 'Mouse sem fio', preco: 149.9 },
];
const skusDoPedido = ['TEC-001', 'MOU-014', 'MON-007'];
const itens = skusDoPedido.map((sku) => catalogo.find((p) => p.sku === sku));
const total = itens.reduce((soma, item) => soma + item.preco, 0);
console.log(total);Repare no rastro: a linha que aparece duas vezes com colunas diferentes é o
callback por dentro e a chamada do reduce por fora. O MON-007 não está no
catálogo, o find devolveu undefined, e o map guardou esse undefined
como se fosse um item. O contexto completo desse erro está em
TypeError: Cannot read properties of undefined.
A correção não é só filtrar o buraco: é saber qual SKU sumiu, porque isso costuma ser um problema de dados, não de código.
const catalogo = [
{ sku: 'TEC-001', nome: 'Teclado mecânico', preco: 289.9 },
{ sku: 'MOU-014', nome: 'Mouse sem fio', preco: 149.9 },
];
const skusDoPedido = ['TEC-001', 'MOU-014', 'MON-007'];
const encontrados = skusDoPedido
.map((sku) => catalogo.find((p) => p.sku === sku))
.filter((p) => p !== undefined);
const ausentes = skusDoPedido.filter((sku) => !catalogo.some((p) => p.sku === sku));
console.log(encontrados.reduce((soma, item) => soma + item.preco, 0).toFixed(2));
console.log('SKUs fora do catálogo:', ausentes);Como montar o encadeamento
A ordem das operações muda o custo e a legibilidade. Filtre primeiro, para as etapas seguintes trabalharem com menos itens; transforme depois; e deixe a redução por último, porque é ela que fecha o resultado. Ordenar, quando entra, vem antes do corte — não faz sentido pegar os cinco primeiros de uma lista desordenada, como mostra ordenar array com sort.
Uma última regra prática: quando o encadeamento passa de três métodos, dê nome
às etapas intermediárias com const. Ninguém consegue depurar uma expressão de
seis linhas sem quebrá-la em pedaços — e o pedaço com nome é o que aparece
legível no console.log.
Como microprática, adicione ao pedido um item de categoria monitor com
quantidade 2. Calcule, em variáveis separadas, a lista filtrada dessa categoria
e o total dela. O teste passa se a lista tiver dois registros de monitor e se o
total considerar preco * quantidade, não apenas a quantidade de linhas.
Se os objetos em si ainda são novidade — como criar, acessar por colchetes e alterar propriedade —, o ponto de partida é objetos em JavaScript. O guia completo de JavaScript mostra a ordem de estudo inteira.
Prefere aprender em vídeo?
Tem uma aula sobre este assunto no nosso canal.
Perguntas frequentes
Por que o reduce precisa do zero no final?
Object.groupBy ou reduce para agrupar?
Como remover objetos duplicados de um array?
reduce é mais rápido que um for comum?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Array.prototype.reduce() — developer.mozilla.org
- MDN — Object.groupBy() — developer.mozilla.org
- MDN — Map — developer.mozilla.org



