À partir d'un tableau d'objets, extrayez la valeur d'une propriété en tant que tableau


1019

J'ai un tableau d'objets JavaScript avec la structure suivante:

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

Je veux extraire un champ de chaque objet et obtenir un tableau contenant les valeurs, par exemple un champ foodonnerait un tableau[ 1, 3, 5 ] .

Je peux le faire avec cette approche triviale:

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-t-il une manière plus élégante ou idiomatique de le faire, de sorte qu'une fonction utilitaire personnalisée ne serait pas nécessaire?


Remarque sur les doublons suggérés , il explique comment convertir un seul objet en tableau.


4
La bibliothèque Prototype a ajouté une fonction "pluck" au prototype Array (je pense), pour que vous puissiez écrire var foos = objArray.pluck("foo");.
Pointy

3
@hyde - jsperf.com/map-vs-native-for-loop - veuillez jeter un oeil à cela, espérons que la boucle en elle-même sera une bonne solution
N20084753

1
@ N20084753 pour un test équitable, vous devez également comparer la Array.prototype.mapfonction native là où elle existe
Alnitak

@Alnitak - vous avez raison, mais je ne comprends pas comment le natif Array.prototype.map est optimisé par rapport au natif pour la boucle, de toute façon nous devons traverser le tableau complet.
N20084753

3
OP, je préfère votre approche à toute autre suggestion. Rien de mal à cela.

Réponses:


1337

Voici un moyen plus court d'y parvenir:

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

OU

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

Vous pouvez également vérifier Array.prototype.map().


3
Eh bien, c'est la même chose que le commentaire d'une autre réponse de totymedli, mais néanmoins c'est en fait mieux (à mon avis) que dans les autres réponses , donc ... Changer en réponse acceptée.
hyde

4
Les fonctions de @PauloRoberto Arrow sont fondamentalement prises en charge partout sauf IE.
tgies

1
Bien sûr, c'est autorisé, mais à mon humble avis, rien ne rend cette réponse objectivement meilleure, sauf qu'elle utilise une syntaxe qui n'était pas disponible au moment où vous avez posé la question et qui n'est même pas prise en charge par certains navigateurs. Je noterais également que cette réponse est une copie directe des commentaires qui ont été faits sur la réponse initialement acceptée près d'un an avant la publication de cette réponse.
Alnitak

26
@Alnitak L'utilisation de nouvelles fonctionnalités, à mon avis, est objectivement meilleure. Cet extrait est extrêmement courant, donc je ne suis pas convaincu que ce soit du plagiat. Il n'est pas vraiment utile de garder les réponses obsolètes épinglées en haut.
Rob

6
les commentaires ne sont pas des réponses, imo si quelqu'un poste un commentaire au lieu d'une réponse, c'est sa faute si quelqu'un le copie dans une réponse.
Aequitas

619

Oui, mais il repose sur une fonctionnalité ES5 de JavaScript. Cela signifie qu'il ne fonctionnera pas dans IE8 ou une version antérieure.

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

Sur les interprètes JS compatibles ES6, vous pouvez utiliser une fonction de flèche pour plus de brièveté:

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

Documentation Array.prototype.map


5
Cette solution semble être soignée et simple mais est-elle optimisée par rapport à la méthode de boucle simple.
N20084753

l'OP ne veut-il pas qu'une méthode obtienne un champ, pas seulement un code dur foo?
Alnitak

2
dans ES6, vous pouvez le fairevar result = objArray.map((a) => (a.foo));
Black

28
@Black Encore mieux:var result = objArray.map(a => a.foo);
totymedli

4
@FaizKhan Remarquez l'année. Cette réponse a été publiée le 25 octobre ... 2013. La réponse "acceptée" a été publiée le 11 octobre 2017. En tout cas, il est remarquable que la coche soit allée à l'autre.
Niet the Dark Absol

52

Découvrez la_.pluck() fonction de Lodash ou la fonction Underscore_.pluck() . Les deux font exactement ce que vous voulez en un seul appel de fonction!

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

Mise à jour: _.pluck()a été supprimée à partir de Lodash v4.0.0 , en faveur de _.map()en combinaison avec quelque chose de similaire à la réponse de Niet . _.pluck()est toujours disponible dans Underscore .

Mise à jour 2: Comme Mark le souligne dans les commentaires , quelque part entre Lodash v4 et 4.3, une nouvelle fonction a été ajoutée qui fournit à nouveau cette fonctionnalité. _.property()est une fonction abrégée qui renvoie une fonction pour obtenir la valeur d'une propriété dans un objet.

En outre, _.map()permet désormais de passer une chaîne en tant que deuxième paramètre, qui est passé dans _.property(). Par conséquent, les deux lignes suivantes sont équivalentes à l'exemple de code ci-dessus de pré-Lodash 4.

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

_.property(), et donc _.map(), vous permet également de fournir une chaîne ou un tableau séparé par des points afin d'accéder aux sous-propriétés:

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

Les deux _.map()appels dans l'exemple ci-dessus seront renvoyés [5, 2, 9].

Si vous êtes un peu plus dans la programmation fonctionnelle, jetez un œil à la R.pluck() fonction de Ramda , qui ressemblerait à quelque chose comme ceci:

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

5
Bonne nouvelle: quelque part entre Lodash 4.0.0 et 4.3.0 _.property('foo')( lodash.com/docs#property ) a été ajouté comme raccourci pour function(o) { return o.foo; }. Cela raccourcit le cas d'utilisation de Lodash à var result = _.pluck(objArray, _.property('foo'));Pour plus de commodité, la _.map() méthode de Lodash 4.3.0 permet également une utilisation sténographique _.property()sous le capot, ce qui entraînevar result = _.map(objArray, 'foo');
Mark A. Fitzgerald

45

En parlant des solutions JS uniquement, j'ai trouvé que, aussi élégante soit-elle, une simple forboucle indexée est plus performante que ses alternatives.

Extraction d'une propriété unique à partir d'un tableau d'éléments 100000 (via jsPerf)

Traditionnel pour boucle 368 Ops / sec

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

ES6 pour..de boucle 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 () est lent, mais n'hésitez pas à l'utiliser si vous pensez que la lisibilité vaut plus que les performances.

Edit # 2: 6/2019 - Lien jsPerf cassé, supprimé.


1
Appelez-moi comme vous voulez, mais la performance compte. Si vous pouvez comprendre la carte, vous pouvez comprendre une boucle for.
illcrx

Bien qu'il soit utile de connaître vos résultats d'analyse comparative, je pense que c'est inapproprié ici, car cela ne répond pas à cette question. Pour l'améliorer, ajoutez également une vraie réponse, ou bien compressez-la dans un commentaire (si possible).
Ifedi Okonkwo

16

Il est préférable d'utiliser une sorte de bibliothèque comme lodash ou underscore pour une assurance multi-navigateur.

Dans Lodash, vous pouvez obtenir les valeurs d'une propriété dans un tableau en suivant la méthode

_.map(objArray,"foo")

et dans Underscore

_.pluck(objArray,"foo")

Les deux reviendront

[1, 2, 3]

15

En utilisant Array.prototype.map:

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

Voir le lien ci-dessus pour une cale pour les navigateurs pré-ES5.


10

Dans ES6, vous pouvez faire:

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

Et si foo n'est pas défini? Un tableau d'indéfinis n'est pas bon.
godhar

6

Bien mapqu'il s'agisse d'une bonne solution pour sélectionner des «colonnes» dans une liste d'objets, elle présente un inconvénient. S'il n'est pas explicitement vérifié si les colonnes existent ou non, cela générera une erreur et (au mieux) vous fournira undefined. J'opterais pour une reducesolution, qui peut simplement ignorer la propriété ou même vous définir une valeur par défaut.

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

exemple jsbin

Cela fonctionnerait même si l'un des éléments de la liste fournie n'est pas un objet ou ne contient pas le champ.

Il peut même être rendu plus flexible en négociant une valeur par défaut si un élément n'est pas un objet ou ne contient pas le champ.

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

exemple jsbin

Ce serait la même chose avec la carte, car la longueur du tableau retourné serait la même que celle du tableau fourni. (Dans ce cas, a mapest légèrement moins cher 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;
    }, []);
}

exemple jsbin

Et puis il y a la solution la plus flexible, celle qui vous permet de basculer entre les deux comportements simplement en fournissant une valeur alternative.

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

exemple jsbin

Comme les exemples ci-dessus (espérons-le) éclairent la façon dont cela fonctionne, permet de raccourcir un peu la Array.concatfonction en utilisant la fonction.

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

exemple jsbin


6

En général, si vous souhaitez extrapoler des valeurs d'objet qui sont à l'intérieur d'un tableau (comme décrit dans la question), vous pouvez utiliser la déstructuration de réduction, de mappage et de tableau.

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'équivalent utilisant for in loop serait:

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

Sortie produite: ["mot", "encore", "certains", "1", "2", "3"]


3

Cela dépend de votre définition de «mieux».

Les autres réponses soulignent l'utilisation de la carte, qui est naturelle (surtout pour les gars habitués au style fonctionnel) et concise. Je recommande fortement de l'utiliser (si vous ne vous embêtez pas avec les quelques gars d'IE8-IT). Donc, si "mieux" signifie "plus concis", "maintenable", "compréhensible", alors oui, c'est bien mieux.

En revanche, cette beauté ne va pas sans coûts supplémentaires. Je ne suis pas un grand fan de microbench, mais j'ai mis un petit test ici . Les résultats sont prévisibles, l'ancienne méthode laide semble être plus rapide que la fonction de carte. Donc, si "mieux" signifie "plus vite", alors non, restez avec la vieille école.

Encore une fois, ce n'est qu'un microbench et en aucun cas contre l'utilisation de map, c'est juste mes deux cents :).


Eh bien, c'est en fait côté serveur, pas dans un navigateur, donc IE8 n'est pas pertinent. Oui, mapc'est la façon la plus évidente d'aller, d'une manière ou d'une autre, je n'ai tout simplement pas trouvé avec google (j'ai trouvé la carte de jquery, ce qui n'est pas pertinent).
hyde

3

Si vous souhaitez également prendre en charge les objets de type tableau, utilisez Array.from ( ES2015 ):

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

L'avantage qu'il a sur la méthode Array.prototype.map () est que l'entrée peut également être un ensemble :

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

2

Si vous voulez plusieurs valeurs dans ES6 +, ce qui suit fonctionnera

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

Cela fonctionne car {foo, baz}à gauche utilise la déstructuration d'objets et sur le côté droit de la flèche est équivalent en {foo: foo, baz: baz}raison des littéraux d'objets améliorés d' ES6 .


juste ce dont j'avais besoin, à quel point pensez-vous que cette solution est rapide / lente?
Ruben

1
@Ruben Pour dire vraiment, je pense que vous auriez besoin de prendre différentes options sur cette page et de les tester en utilisant quelque chose comme jsperf.com
Chris Magnuson

1

La carte des fonctions est un bon choix pour les tableaux d'objets. Bien qu'un certain nombre de bonnes réponses aient déjà été publiées, l'exemple d'utilisation de la carte avec une combinaison avec un filtre peut être utile.

Si vous souhaitez exclure les propriétés dont les valeurs ne sont pas définies ou exclure uniquement une propriété spécifique, vous pouvez procéder comme suit:

    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 réponse fournie ci-dessus est bonne pour extraire une seule propriété, que se passe-t-il si vous souhaitez extraire plusieurs propriétés d'un tableau d'objets. Voici la solution !! Dans ce cas, nous pouvons simplement utiliser _.pick (objet, [chemins])

_.pick(object, [paths])

Supposons que objArray possède des objets avec trois propriétés comme ci-dessous

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

Maintenant, nous voulons extraire la propriété foo et bar de chaque objet et les stocker dans un tableau séparé. Nous allons d'abord itérer les éléments du tableau à l'aide de la carte, puis nous y appliquerons la méthode Lodash Library Standard _.pick ().

Nous pouvons maintenant extraire les propriétés 'foo' et 'bar'.

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

et le résultat serait [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

prendre plaisir!!!


1
D'où _.pickvient-il? Ce n'est pas une fonction standard.
neves

Je viens de mettre à jour la réponse, _.pick () est une méthode standard de la bibliothèque Lodash.
Akash Jain

0

Si vous avez des tableaux imbriqués, vous pouvez le faire fonctionner comme ceci:

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)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.