Da una matrice di oggetti, estrarre il valore di una proprietà come matrice


1019

Ho un array di oggetti JavaScript con la seguente struttura:

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

Voglio estrarre un campo da ogni oggetto e ottenere una matrice contenente i valori, ad esempio campo foodarebbe matrice [ 1, 3, 5 ].

Posso farlo con questo approccio banale:

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 ]

Esiste un modo più elegante o idiomatico per farlo, in modo che una funzione di utilità personalizzata non sia necessaria?


Nota sul duplicato suggerito , illustra come convertire un singolo oggetto in un array.


4
La libreria Prototype ha aggiunto una funzione "pluck" al prototipo di array (credo), in modo da poter scrivere var foos = objArray.pluck("foo");.
Punta a punta il

3
@hyde - jsperf.com/map-vs-native-for-loop - per favore, dai un'occhiata a questo, spero che il looping stesso sia una buona soluzione
N20084753

1
@ N20084753 per un test corretto dovresti anche confrontare la Array.prototype.mapfunzione nativa in cui esiste
Alnitak,

@Alnitak - hai ragione ma non riesco a capire come Array.prototype.map nativo sia ottimizzato rispetto a quello nativo per il ciclo, in qualunque modo dobbiamo attraversare l'array completo.
N20084753,

3
OP, preferisco il tuo approccio a tutti gli altri che sono stati suggeriti. Niente di sbagliato.

Risposte:


1336

Ecco un modo più breve per raggiungerlo:

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

O

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

Puoi anche controllare Array.prototype.map().


3
Bene, questo è lo stesso del commento di un'altra risposta di totymedli, ma nondimeno è in realtà migliore (secondo me) rispetto ad altre risposte , quindi ... Cambiandolo in risposta accettata.
hyde,

4
Le funzioni di @PauloRoberto Arrow sono sostanzialmente supportate ovunque tranne IE.
tgies,

1
certo, è permesso, ma IMHO non ha nulla che renda questa risposta obiettivamente migliore, tranne per il fatto che utilizza una sintassi che non era disponibile al momento in cui hai posto la domanda e non è nemmeno supportata in alcuni browser. Vorrei anche notare che questa risposta è una copia diretta dei commenti che sono stati fatti sulla risposta inizialmente accettata quasi un anno prima che questa risposta fosse pubblicata.
Alnitak,

26
@Alnitak L'utilizzo delle funzionalità più recenti, dal mio punto di vista, è oggettivamente migliore. Questo frammento è estremamente comune, quindi non sono convinto che si tratti di plagio. Non c'è davvero alcun valore nel mantenere le risposte obsolete appuntate in alto.
Rob

6
i commenti non sono risposte, quindi se qualcuno pubblica un commento invece di una risposta, è colpa loro se qualcuno lo copia in una risposta.
Aequitas

619

Sì, ma si basa su una funzione ES5 di JavaScript. Ciò significa che non funzionerà in IE8 o precedenti.

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

Sugli interpreti JS compatibili con ES6 è possibile utilizzare una funzione freccia per brevità:

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

Documentazione Array.prototype.map


5
Questa soluzione sembra essere ordinata e semplice ma è ottimizzata rispetto al semplice metodo di looping.
N20084753,

l'OP non vuole un metodo per ottenere un campo, non solo hardcoded foo?
Alnitak,

2
in ES6 puoi farevar result = objArray.map((a) => (a.foo));
Black

28
Ancora meglio:var result = objArray.map(a => a.foo);
totymedli,

4
@FaizKhan Nota l'anno. Questa risposta è stata pubblicata il 25 ottobre ... 2013. La risposta "accettata" è stata pubblicata l'11 ottobre 2017. Semmai è notevole che il segno di spunta sia andato sull'altra.
Niet the Dark Absol,

52

Scopri la_.pluck() funzione di Lodash o Underscore_.pluck() . Entrambi fanno esattamente quello che vuoi in una singola chiamata di funzione!

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

Aggiornamento: _.pluck()è stato rimosso da Lodash v4.0.0 , a favore di _.map()in combinazione con qualcosa di simile alla risposta di Niet . _.pluck()è ancora disponibile in Underscore .

Aggiornamento 2: Come sottolinea Mark nei commenti , a metà tra Lodash v4 e 4.3, è stata aggiunta una nuova funzione che fornisce nuovamente questa funzionalità. _.property()è una funzione abbreviata che restituisce una funzione per ottenere il valore di una proprietà in un oggetto.

Inoltre, _.map()ora consente di passare una stringa come secondo parametro, a cui viene passato _.property(). Di conseguenza, le seguenti due righe sono equivalenti all'esempio di codice sopra di pre-Lodash 4.

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

_.property()e, quindi _.map(), consentono anche di fornire una stringa o un array separati da punti per accedere alle proprietà secondarie:

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']));

Entreranno entrambe le _.map()chiamate nell'esempio sopra [5, 2, 9].

Se sei un po 'più interessato alla programmazione funzionale, dai un'occhiata alla funzione di Ramda R.pluck() , che sarebbe simile a questa:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)

5
Buone notizie: da qualche parte tra Lodash 4.0.0 e 4.3.0 _.property('foo')( lodash.com/docs#property ) è stata aggiunta una scorciatoia per function(o) { return o.foo; }. Questo accorcia il caso d'uso di Lodash a var result = _.pluck(objArray, _.property('foo'));Per ulteriore comodità, il _.map() metodo di Lodash 4.3.0 consente anche l'uso di una scorciatoia _.property()sotto il cofano, risultando invar result = _.map(objArray, 'foo');
Mark A. Fitzgerald

45

Parlando delle sole soluzioni JS, ho scoperto che, per quanto non elegante, un semplice forciclo indicizzato è più performante delle sue alternative.

Estrazione di una singola proprietà da un array di elementi 100000 (tramite jsPerf)

Tradizionale per loop 368 Ops / sec

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

ES6 per..di circuito 303 Ops / sec

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

Array.prototype.map 19 Ops / sec

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

TL; DR - .map () è lento, ma sentiti libero di usarlo se ritieni che la leggibilità valga più delle prestazioni.

Modifica n. 2: 6/2019 - collegamento jsPerf interrotto, rimosso.


1
Chiamami come preferisci, ma le prestazioni contano. Se riesci a capire la mappa puoi capire un ciclo for.
illcrx,

Sebbene sia utile conoscere i risultati del tuo benchmarking, penso che sia inappropriato qui, dal momento che non risponde a questa domanda. Per migliorarlo, aggiungi anche una risposta reale, oppure comprimila in un commento (se possibile).
Ifedi Okonkwo,

16

È meglio usare una sorta di librerie come lodash o underscore per l'assicurazione cross-browser.

In Lodash puoi ottenere i valori di una proprietà nella matrice seguendo il metodo

_.map(objArray,"foo")

e in Underscore

_.pluck(objArray,"foo")

Entrambi torneranno

[1, 2, 3]

15

Utilizzando Array.prototype.map:

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

Vedi il link sopra per uno shim per i browser pre-ES5.


10

In ES6, puoi fare:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)

Che dire se il foo non è definito? Una serie di indefiniti non va bene.
Godhar,

6

Mentre mapè una soluzione adeguata per selezionare "colonne" da un elenco di oggetti, ha un aspetto negativo. Se non viene verificato esplicitamente l'esistenza delle colonne, verrà generato un errore e (nella migliore delle ipotesi) fornito undefined. Opterei per una reducesoluzione, che può semplicemente ignorare la proprietà o persino impostarti con un valore predefinito.

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;
    }, []);
}

esempio jsbin

Ciò funzionerebbe anche se uno degli elementi nell'elenco fornito non è un oggetto o non contiene il campo.

Può anche essere reso più flessibile negoziando un valore predefinito nel caso in cui un articolo non sia un oggetto o non contenga il 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;
    }, []);
}

esempio jsbin

Questo sarebbe lo stesso con map, poiché la lunghezza dell'array restituito sarebbe la stessa dell'array fornito. (Nel qual caso a mapè leggermente più economico di 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;
    }, []);
}

esempio jsbin

E poi c'è la soluzione più flessibile, quella che ti consente di passare da un comportamento all'altro semplicemente fornendo un valore 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;
    }, []);
}

esempio jsbin

Mentre gli esempi sopra (si spera) fanno luce sul modo in cui funziona, accorciamo un po 'la Array.concatfunzione utilizzando la funzione.

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 : []));
    }, []);
}

esempio jsbin


6

In generale, se si desidera estrapolare i valori degli oggetti che si trovano all'interno di un array (come descritto nella domanda), è possibile utilizzare la riduzione, la mappatura e la distruzione dell'array.

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)

L'utilizzo equivalente di for in loop sarebbe:

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

Output prodotto: ["word", "again", "some", "1", "2", "3"]


3

Dipende dalla tua definizione di "migliore".

Le altre risposte sottolineano l'uso della mappa, che è naturale (soprattutto per i ragazzi abituati allo stile funzionale) e concisa. Consiglio vivamente di usarlo (se non ti preoccupi dei pochi ragazzi di IE8-IT). Quindi, se "migliore" significa "più conciso", "mantenibile", "comprensibile", allora sì, è molto meglio.

D'altra parte, questa bellezza non arriva senza costi aggiuntivi. Non sono un grande fan di microbench, ma ho messo su un piccolo test qui . Il risultato è prevedibile, il vecchio brutto modo sembra essere più veloce della funzione della mappa. Quindi, se "meglio" significa "più veloce", allora no, resta alla moda della vecchia scuola.

Ancora una volta questo è solo un microbench e in nessun modo a favore dell'uso di map, sono solo i miei due centesimi :).


Bene, questo è in realtà lato server, non in un browser, quindi IE8 è irrilevante. Sì, mapè la strada più ovvia da fare, in qualche modo non sono riuscito a trovarlo con google (ho trovato solo la mappa di jquery, che non è pertinente).
hyde,

3

Se si desidera supportare anche oggetti simili a array, utilizzare Array.from (ES2015):

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

Il vantaggio che ha rispetto al metodo Array.prototype.map () è che l'input può anche essere un set :

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

2

Se si desidera più valori in ES6 +, funzionerà quanto segue

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 }))

Questo funziona come {foo, baz}a sinistra sta usando l' oggetto destruttore e sul lato destro della freccia è equivalente a {foo: foo, baz: baz}causa dei letterali di oggetti avanzati ES6 .


proprio quello di cui avevo bisogno, quanto pensi che sia veloce / lento questa soluzione?
Ruben,

1
@Ruben Per dirlo davvero penso che dovresti prendere varie opzioni da questa pagina e metterle nei test usando qualcosa come jsperf.com
Chris Magnuson,

1

La mappa delle funzioni è una buona scelta quando si tratta di matrici di oggetti. Sebbene siano già state pubblicate numerose risposte valide, l'esempio dell'utilizzo della mappa con la combinazione con il filtro potrebbe essere utile.

Nel caso in cui si desideri escludere le proprietà i cui valori non sono definiti o escludere solo una proprietà specifica, è possibile effettuare le seguenti operazioni:

    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/


0

La risposta sopra fornita è utile per l'estrazione di una singola proprietà, cosa succede se si desidera estrarre più di una proprietà dalla matrice di oggetti. Ecco la soluzione !! In questo caso possiamo semplicemente usare _.pick (oggetto, [percorsi])

_.pick(object, [paths])

Supponiamo che objArray abbia oggetti con tre proprietà come di seguito

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

Ora vogliamo estrarre la proprietà foo e bar da ogni oggetto e memorizzarli in un array separato. Per prima cosa itereremo gli elementi dell'array usando map e poi applicheremo il metodo _.pick () di Lodash Library Standard.

Ora siamo in grado di estrarre la proprietà "foo" e "bar".

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

e il risultato sarebbe [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

godere!!!


1
Da dove _.pickviene Non è una funzione standard.
neves

Ho appena aggiornato la risposta, _.pick () è un metodo standard della libreria Lodash.
Akash Jain,

0

Se hai matrici nidificate puoi farlo funzionare in questo modo:

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)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.