web-dev-qa-db-fra.com

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

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 souhaite extraire un tableau contenant les valeurs de la clé foo, ce qui donne la valeur [ 1, 3, 5 ].

J'ai accompli cela avec l'approche triviale, comme suit:

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 méthode plus élégante et plus performante?


Remarque sur suggestion de dupliquer , cette question demande comment convertir un objet en un tableau, cette question demande comment extraire la propriété unique de un tableau d'objets.

593
hyde

Oui, mais il s’appuie sur une fonctionnalité ES5 de JavaScript. Cela signifie que cela ne fonctionnera pas dans IE8 ou plus ancien.

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

Sur les interprètes JS compatibles ES6, vous pouvez utiliser une fonction arrow par souci de concision:

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

Documentation

563

En ce qui concerne les solutions JS uniquement, j’ai constaté que, même si elle était élégante, une simple boucle indexée for est plus performante que ses alternatives.

https://jsperf.com/extract-prop-from-object-array/

Extraction d'une propriété unique d'un tableau de 100 000 éléments

Traditionnel pour la boucle 368 Ops/sec

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

ES6 pour..de la boucle 303 Ops/sec

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

Edit: Ops/s mis à jour le 10/2017. TL; DR - .map () est lent. Mais parfois, la lisibilité vaut plus que la performance.

32
pscl

Utilisation de Array.prototype.map :

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

Voir le lien ci-dessus pour un aperçu des navigateurs pré-ES5.

15
Alnitak

Il est préférable d’utiliser une sorte de bibliothèque telle que lodash ou un soulignement pour une assurance entre navigateurs.

En lodash, vous pouvez obtenir les valeurs d'une propriété dans un tableau en appliquant la méthode

_.map(objArray,"foo")

et en soulignement 

_.pluck(objArray,"foo")

les deux reviendront [1, 3, 5]

9
Tejas Patel

Bien que map soit une solution appropriée pour sélectionner des "colonnes" dans une liste d'objets, il 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. Je choisirais une solution reduce, qui peut simplement ignorer la propriété ou même vous configurer avec 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 map, car la longueur du tableau retourné serait la même que celle du tableau fourni. (Dans ce cas, une map est légèrement moins chère qu'une 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

La solution la plus flexible est celle qui permet de basculer d'un comportement à l'autre en fournissant simplement 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) jettent un peu de lumière sur la façon dont cela fonctionne, raccourcissons un peu la fonction en utilisant la fonction Array.concat.

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

5
Rogier Spieker

Dans ES6, vous pouvez faire:

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

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 souciez pas des quelques gars d'IE8- IT). Donc, si "mieux" signifie "plus concis", "maintenable", "compréhensible", alors oui, c'est bien mieux.

D'autre part, cette beauté ne va pas sans coûts supplémentaires. Je ne suis pas un grand fan de microbench, mais j’ai fait 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 à la mode old school.

Encore une fois, il s’agit d’une micro-table et nous ne défendons nullement l’utilisation de map, c’est juste mes deux cents :).

2
sitifensys

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 Set:

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

En général, si vous souhaitez extrapoler les valeurs d'objet contenues dans un tableau (comme décrit dans la question), vous pouvez utiliser la réduction, la carte et la déstructuration du tableau.

ES6

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

L'équivalent en utilisant pour dans la boucle serait le suivant:

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

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

1
eugen sunic

La mappe de fonctions est un bon choix lorsqu'il s'agit de 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 la combinaison avec le 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, procédez 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/

1
Niko Gamulin

La réponse fournie ci-dessus est utile pour extraire une propriété unique, et 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 a 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 les propriétés 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 en utilisant map, puis nous y appliquons la méthode _.pick () de la bibliothèque Lodash Standard. 

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!!!

0
Akash Jain

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 de la manière suivante: {foo, baz} à gauche utilise object destructoring et à droite de la flèche équivaut à {foo: foo, baz: baz} en raison de les littéraux d'objet améliorés de ES6 .

0
Chris Magnuson