De uma matriz de objetos, extraia o valor de uma propriedade como matriz

1019

Eu tenho uma matriz de objetos JavaScript com a seguinte estrutura:

objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];

Eu quero extrair um campo de cada objeto e obter uma matriz contendo os valores, por exemplo, campo foodaria matriz [ 1, 3, 5 ].

Eu posso fazer isso com esta abordagem trivial:

function getFields(input, field) {
    var output = [];
    for (var i=0; i < input.length ; ++i)
        output.push(input[i][field]);
    return output;
}

var result = getFields(objArray, "foo"); // returns [ 1, 3, 5 ]

Existe uma maneira mais elegante ou idiomática de fazer isso, para que uma função de utilitário personalizada seja desnecessária?


Nota sobre duplicado sugerido , ele aborda como converter um único objeto em uma matriz.

hyde
fonte
4
A biblioteca Prototype adicionou uma função "arrancar" ao protótipo Array (eu acho), para que você possa escrever var foos = objArray.pluck("foo");.
quer
3
@hyde - jsperf.com/map-vs-native-for-loop - por favor, ter um olhar para isso, espero planície looping si uma boa solução
N20084753
1
@ N20084753 para um teste justo que você também deve comparar o nativo Array.prototype.mapfunção onde ela existe
Alnitak
@ Alnitak - você está certo, mas não consigo entender como o Array.prototype.map nativo é otimizado que o loop for nativo, de qualquer maneira que precisarmos percorrer a matriz completa.
N20084753
3
OP, prefiro sua abordagem a quaisquer outras sugeridas. Nada de errado com isso.

Respostas:

1336

Aqui está uma maneira mais curta de alcançá-lo:

let result = objArray.map(a => a.foo);

OU

let result = objArray.map(({ foo }) => foo)

Você também pode verificar Array.prototype.map().

Jalasem
fonte
3
Bem, isso é o mesmo que o comentário de outra resposta de totymedli, mas, no entanto, é realmente melhor (na minha opinião) do que em outras respostas , então ... Alterando para resposta aceita.
Hyde
4
As funções @PauloRoberto Arrow são basicamente suportadas em todos os lugares, exceto no IE.
tgies
1
com certeza, é permitido, mas IMHO não há nada que torne essa resposta objetivamente melhor, exceto que ela usa uma sintaxe que não estava disponível no momento em que você fez a pergunta e nem é suportada em alguns navegadores. Eu também observaria que esta resposta é uma cópia direta dos comentários que foram feitos sobre a resposta originalmente aceita quase um ano antes de ela ser publicada.
Alnitak
26
@Alnitak Usar novas funcionalidades, no meu ponto de vista, é objetivamente melhor. Esse snippet é extremamente comum, então não estou convencido de que isso seja plágio. Não há realmente nenhum valor em manter respostas desatualizadas fixadas no topo.
Rob
6
comentários não são respostas; se alguém postar um comentário em vez de uma resposta, a culpa será deles se alguém o copiar em uma resposta.
Aequitas
619

Sim, mas depende de um recurso ES5 do JavaScript. Isso significa que não funcionará no IE8 ou mais antigo.

var result = objArray.map(function(a) {return a.foo;});

Nos intérpretes JS compatíveis com ES6, você pode usar uma função de seta por questões de brevidade:

var result = objArray.map(a => a.foo);

Documentação Array.prototype.map

Niet, o Escuro Absol
fonte
5
Essa solução parece ser pura e simples, mas é otimizada que o método de loop simples.
N20084753
o OP não quer um método para obter nenhum campo, não apenas codificado foo?
Alnitak
2
no ES6 você pode fazervar result = objArray.map((a) => (a.foo));
Black
28
@Black Ainda melhor:var result = objArray.map(a => a.foo);
totymedli
4
@FaizKhan Observe o ano. Esta resposta foi publicada em 25 de outubro de 2013. A resposta "aceita" foi publicada em 11 de outubro de 2017. Se alguma coisa é notável que a marca de seleção foi para a outra.
Niet the Dark Absol
52

Confira a_.pluck() função de Lodash ou a_.pluck() função de sublinhado . Ambos fazem exatamente o que você quer em uma única chamada de função!

var result = _.pluck(objArray, 'foo');

Atualização: _.pluck()foi removida a partir do Lodash v4.0.0 , em favor de _.map()em combinação com algo semelhante à resposta de Niet . _.pluck()ainda está disponível no Underscore .

Atualização 2: como Mark aponta nos comentários , em algum lugar entre o Lodash v4 e o 4.3, foi adicionada uma nova função que fornece essa funcionalidade novamente. _.property()é uma função abreviada que retorna uma função para obter o valor de uma propriedade em um objeto.

Além disso, _.map()agora permite que uma string seja passada como o segundo parâmetro, que é passado para _.property(). Como resultado, as duas linhas a seguir são equivalentes ao exemplo de código acima do pré-Lodash 4.

var result = _.map(objArray, 'foo');
var result = _.map(objArray, _.property('foo'));

_.property()e, portanto _.map(), também permite que você forneça uma string ou matriz separada por pontos para acessar subpropriedades:

var objArray = [
    {
        someProperty: { aNumber: 5 }
    },
    {
        someProperty: { aNumber: 2 }
    },
    {
        someProperty: { aNumber: 9 }
    }
];
var result = _.map(objArray, _.property('someProperty.aNumber'));
var result = _.map(objArray, _.property(['someProperty', 'aNumber']));

As duas _.map()chamadas no exemplo acima retornarão [5, 2, 9].

Se você gosta um pouco mais de programação funcional, dê uma olhada na função Ramda R.pluck() , que seria algo como isto:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)
cpdt
fonte
5
Boas notícias: em algum lugar entre o Lodash 4.0.0 e o 4.3.0 _.property('foo')( lodash.com/docs#property ) foi adicionado como uma abreviação de function(o) { return o.foo; }. Isto encurta o caso de uso de Lodash var result = _.pluck(objArray, _.property('foo'));Para maior conveniência, Lodash 4.3.0 do _.map() método também permite uma forma abreviada usando _.property()sob o capô, resultando emvar result = _.map(objArray, 'foo');
Mark A. Fitzgerald
45

Falando apenas nas soluções JS, descobri que, por mais deselegante que seja, um forloop indexado simples tem mais desempenho do que suas alternativas.

Extraindo propriedade única de uma matriz de 100000 elementos (via jsPerf)

Tradicional para loop 368 Ops / s

var vals=[];
for(var i=0;i<testArray.length;i++){
   vals.push(testArray[i].val);
}

ES6 for..of loop 303 Ops / s

var vals=[];
for(var item of testArray){
   vals.push(item.val); 
}

Array.prototype.map 19 Ops / s

var vals = testArray.map(function(a) {return a.val;});

TL; DR - .map () é lento, mas fique à vontade para usá-lo se achar que a legibilidade vale mais que o desempenho.

Edit # 2: 6/2019 - Link do jsPerf quebrado, removido.

pscl
fonte
1
Ligue para mim como quiser, mas o desempenho conta. Se você consegue entender o mapa, pode entender um loop for.
Illrx
Embora seja útil conhecer seus resultados de benchmarking, acho inadequado aqui, pois não responde a essa pergunta. Para melhorá-lo, adicione uma resposta real também ou compacte-a em um comentário (se possível).
Ifedi Okonkwo
16

É melhor usar algum tipo de bibliotecas como lodash ou sublinhado para garantir a navegação entre navegadores.

No Lodash, você pode obter valores de uma propriedade na matriz, seguindo o método

_.map(objArray,"foo")

e em sublinhado

_.pluck(objArray,"foo")

Ambos retornarão

[1, 2, 3]
Tejas Patel
fonte
15

Usando Array.prototype.map:

function getFields(input, field) {
    return input.map(function(o) {
        return o[field];
    });
}

Consulte o link acima para obter uma correção para os navegadores anteriores ao ES5.

Alnitak
fonte
10

No ES6, você pode fazer:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
Yuxuan Chen
fonte
E se foo não estiver definido? Uma matriz indefinida não é boa.
godhar 21/04
6

Embora mapseja uma solução adequada para selecionar 'colunas' de uma lista de objetos, ela tem uma desvantagem. Se não for verificado explicitamente se as colunas existem ou não, ocorrerá um erro e (na melhor das hipóteses) fornecerá a você undefined. Eu optaria por uma reducesolução, que pode simplesmente ignorar a propriedade ou até configurá-lo com um valor padrão.

function getFields(list, field) {
    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  check if the item is actually an object and does contain the field
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

exemplo jsbin

Isso funcionaria mesmo se um dos itens da lista fornecida não for um objeto ou não contiver o campo.

Pode até ser mais flexível negociando um valor padrão, caso um item não seja um objeto ou não contenha o campo.

function getFields(list, field, otherwise) {
    //  reduce the provided list to an array containing either the requested field or the alternative value
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        carry.push(typeof item === 'object' && field in item ? item[field] : otherwise);

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

exemplo jsbin

Isso seria o mesmo com o mapa, pois o comprimento da matriz retornada seria o mesmo que a matriz fornecida. (Nesse caso, a mapé um pouco mais barato que a reduce):

function getFields(list, field, otherwise) {
    //  map the provided list to an array containing either the requested field or the alternative value
    return list.map(function(item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        return typeof item === 'object' && field in item ? item[field] : otherwise;
    }, []);
}

exemplo jsbin

E há a solução mais flexível, que permite alternar entre os dois comportamentos simplesmente fornecendo um valor alternativo.

function getFields(list, field, otherwise) {
    //  determine once whether or not to use the 'otherwise'
    var alt = typeof otherwise !== 'undefined';

    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of 'otherwise' if it was provided
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }
        else if (alt) {
            carry.push(otherwise);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

exemplo jsbin

Como os exemplos acima (esperançosamente) lançam alguma luz sobre o modo como isso funciona, vamos encurtar um pouco a Array.concatfunção utilizando a função.

function getFields(list, field, otherwise) {
    var alt = typeof otherwise !== 'undefined';

    return list.reduce(function(carry, item) {
        return carry.concat(typeof item === 'object' && field in item ? item[field] : (alt ? otherwise : []));
    }, []);
}

exemplo jsbin

Rogier Spieker
fonte
6

Em geral, se você quiser extrapolar valores de objetos que estão dentro de uma matriz (como descrito na pergunta), poderá usar a redução, o mapa e a destruição de matriz.

ES6

let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, obj) => [...acc, Object.values(obj).map(y => y)], []);

console.log(b)

O equivalente utilizando no circuito seria:

for (let i in a) {
  let temp = [];
  for (let j in a[i]) {
    temp.push(a[i][j]);
  }
  array.push(temp);
}

Saída produzida: ["word", "again", "some", "1", "2", "3"]

Eugen Sunic
fonte
3

Depende da sua definição de "melhor".

As outras respostas apontam o uso do mapa, que é natural (especialmente para os homens acostumados ao estilo funcional) e conciso. Eu recomendo fortemente usá-lo (se você não se incomoda com os poucos caras do IE8-IT). Portanto, se "melhor" significa "mais conciso", "sustentável", "compreensível", então sim, é muito melhor.

Por outro lado, essa beleza não vem sem custos adicionais. Não sou muito fã de microbench, mas fiz um pequeno teste aqui . O resultado é previsível, a velha maneira feia parece ser mais rápida que a função de mapa. Portanto, se "melhor" significa "mais rápido", então não, fique com a moda antiga.

Novamente, este é apenas um microbench e, de maneira alguma, defendendo o uso de map, são apenas meus dois centavos :).

sitifensys
fonte
Bem, este é realmente o lado do servidor, não o navegador, portanto o IE8 é irrelevante. Sim, mapé o caminho óbvio, de alguma forma eu apenas não consegui encontrá-lo no google (eu encontrei apenas o mapa do jquery, que não é relevante).
Hyde
3

Se você também deseja oferecer suporte a objetos do tipo matriz, use Array.from (ES2015):

Array.from(arrayLike, x => x.foo);

A vantagem que ele tem sobre o método Array.prototype.map () é que a entrada também pode ser um conjunto :

let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);
TechWisdom
fonte
2

Se você deseja vários valores no ES6 +, o seguinte funcionará

objArray = [ { foo: 1, bar: 2, baz: 9}, { foo: 3, bar: 4, baz: 10}, { foo: 5, bar: 6, baz: 20} ];

let result = objArray.map(({ foo, baz }) => ({ foo, baz }))

Isso funciona, pois {foo, baz}à esquerda está usando a destruição de objetos e no lado direito da seta é equivalente a {foo: foo, baz: baz}devido aos literais de objetos aprimorados do ES6 .

Chris Magnuson
fonte
exatamente o que eu precisava, quão rápido / lento você acha que essa solução é?
Ruben
1
@Ruben Para realmente dizer que eu acho que você precisa tomar várias opções desta página e colocá-los em testes usando algo como jsperf.com
Chris Magnuson
1

O mapa de funções é uma boa opção ao lidar com matrizes de objetos. Embora já existam várias boas respostas, o exemplo do uso de mapa com combinação com filtro pode ser útil.

Caso deseje excluir as propriedades cujos valores são indefinidos ou excluir apenas uma propriedade específica, você pode fazer o seguinte:

    var obj = {value1: "val1", value2: "val2", Ndb_No: "testing", myVal: undefined};
    var keysFiltered = Object.keys(obj).filter(function(item){return !(item == "Ndb_No" || obj[item] == undefined)});
    var valuesFiltered = keysFiltered.map(function(item) {return obj[item]});

https://jsfiddle.net/ohea7mgk/

Niko Gamulin
fonte
0

A resposta fornecida acima é boa para extrair uma única propriedade, e se você quiser extrair mais de uma propriedade da matriz de objetos. Aqui está a solução !! Nesse caso, podemos simplesmente usar _.pick (object, [caminhos])

_.pick(object, [paths])

Vamos supor que objArray tenha objetos com três propriedades, como abaixo

objArray = [ { foo: 1, bar: 2, car:10}, { foo: 3, bar: 4, car:10}, { foo: 5, bar: 6, car:10} ];

Agora queremos extrair a propriedade foo e bar de cada objeto e armazená-los em uma matriz separada. Primeiro, iteraremos os elementos da matriz usando o mapa e, em seguida, aplicaremos o método _.pick () do Lodash Library Standard.

Agora somos capazes de extrair as propriedades 'foo' e 'bar'.

var newArray = objArray.map((element)=>{ return _.pick(element, ['foo','bar'])}) console.log(newArray);

e o resultado seria [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

desfrutar!!!

Akash Jain
fonte
1
De onde _.pickvem? Não é uma função padrão.
Neves
Acabei de atualizar a resposta, _.pick () é um método padrão da biblioteca Lodash.
Akash Jain
0

Se você tiver matrizes aninhadas, poderá fazê-lo funcionar assim:

const objArray = [ 
     { id: 1, items: { foo:4, bar: 2}},
     { id: 2, items: { foo:3, bar: 2}},
     { id: 3, items: { foo:1, bar: 2}} 
    ];

    let result = objArray.map(({id, items: {foo}}) => ({id, foo}))
    
    console.log(result)

Qui-Gon Jinn
fonte