De una matriz de objetos, extraer el valor de una propiedad como matriz

1019

Tengo una matriz de objetos JavaScript con la siguiente estructura:

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

Quiero extraer un campo de cada objeto y obtener una matriz que contenga los valores, por ejemplo, el campo foodaría una matriz [ 1, 3, 5 ].

Puedo hacer esto con este enfoque 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 una forma más elegante o idiomática de hacer esto, de modo que una función de utilidad personalizada sea innecesaria?


Nota sobre el duplicado sugerido , cubre cómo convertir un solo objeto en una matriz.

Hyde
fuente
44
La biblioteca Prototype agregó una función de "punteo" al prototipo Array (creo), para que puedas escribir var foos = objArray.pluck("foo");.
Puntiagudo
3
@hyde - jsperf.com/map-vs-native-for-loop - por favor, eche un vistazo a esto, espero que sea una buena solución
N20084753
1
@ N20084753 para una prueba justa, también debe comparar la Array.prototype.mapfunción nativa donde existe
Alnitak
@Alnitak: sí, tiene razón, pero no puedo entender cómo se optimiza el Array.prototype.map nativo que el bucle nativo para el bucle, de cualquier manera que necesitemos recorrer la matriz completa.
N20084753
3
OP, prefiero su enfoque a cualquier otro que se haya sugerido. No tiene nada de malo.

Respuestas:

1337

Aquí hay una forma más corta de lograrlo:

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

O

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

También puedes consultar Array.prototype.map().

Jalasem
fuente
3
Bueno, esto es lo mismo que el comentario de otra respuesta de totymedli, pero no obstante, en realidad es mejor (en mi opinión) que en otras respuestas , así que ... Cambiarlo a respuesta aceptada.
hyde
44
Las funciones de @PauloRoberto Arrow son básicamente compatibles en todas partes, excepto IE.
tgies
1
seguro, está permitido, pero en mi humilde opinión, no hay nada que haga que esta respuesta sea objetivamente mejor, excepto que usa una sintaxis que no estaba disponible en el momento en que hizo la pregunta y que ni siquiera es compatible con algunos navegadores. También señalaría que esta respuesta es una copia directa de los comentarios que se hicieron sobre la respuesta originalmente aceptada casi un año antes de que se publicara esta respuesta.
Alnitak
26
@Alnitak Usar una funcionalidad más nueva, en mi punto de vista, es objetivamente mejor. Este fragmento es extremadamente común, por lo que no estoy convencido de que esto sea plagio. Realmente no hay ningún valor en mantener las respuestas obsoletas en la parte superior.
Rob
66
los comentarios no son respuestas, si alguien publica un comentario en lugar de una respuesta, es su culpa si alguien lo copia en una respuesta.
Aequitas
619

Sí, pero se basa en una función ES5 de JavaScript. Esto significa que no funcionará en IE8 o versiones anteriores.

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

En los intérpretes JS compatibles con ES6, puede usar una función de flecha por brevedad:

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

Array.prototype.map documentation

Niet the Dark Absol
fuente
55
Esta solución parece ser clara y simple, pero está optimizada que el método de bucle simple.
N20084753
¿El OP no quiere un método para obtener ningún campo, no solo codificado foo?
Alnitak
2
en ES6 que puedes hacervar result = objArray.map((a) => (a.foo));
Negro
28
@Black Aún mejor:var result = objArray.map(a => a.foo);
totymedli
44
@FaizKhan Observe el año. Esta respuesta se publicó el 25 de octubre ... 2013. La respuesta "aceptada" se publicó el 11 de octubre de 2017. En todo caso, es notable que la marca de verificación se haya enviado a la otra.
Niet the Dark Absol
52

Salida de Lodash_.pluck() función o de subrayado_.pluck() función. ¡Ambos hacen exactamente lo que quieres en una sola llamada de función!

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

Actualización: _.pluck()se ha eliminado a partir de Lodash v4.0.0 , a favor de _.map()en combinación con algo similar a la respuesta de Niet . _.pluck()todavía está disponible en Underscore .

Actualización 2: Como Mark señala en los comentarios , en algún lugar entre Lodash v4 y 4.3, se ha agregado una nueva función que brinda esta funcionalidad nuevamente. _.property()es una función abreviada que devuelve una función para obtener el valor de una propiedad en un objeto.

Además, _.map()ahora permite pasar una cadena como el segundo parámetro, que se pasa a _.property(). Como resultado, las dos líneas siguientes son equivalentes al ejemplo de código anterior de pre-Lodash 4.

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

_.property()y, por lo tanto _.map(), también le permiten proporcionar una cadena o matriz separada por puntos para acceder a las subpropiedades:

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

Ambas _.map()llamadas en el ejemplo anterior volverán [5, 2, 9].

Si le gusta un poco más la programación funcional, eche un vistazo a la R.pluck() función de Ramda , que se vería así:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)
cpdt
fuente
55
Buenas noticias: en algún lugar entre Lodash 4.0.0 y 4.3.0 _.property('foo')( lodash.com/docs#property ) se agregó como abreviatura de function(o) { return o.foo; }. Esto acorta el caso de uso de Lodash a var result = _.pluck(objArray, _.property('foo'));Para mayor comodidad, el _.map() método de Lodash 4.3.0 también permite un uso abreviado _.property()debajo del capó, lo que resulta envar result = _.map(objArray, 'foo');
Mark A. Fitzgerald
45

Hablando de las soluciones solo para JS, descubrí que, por poco elegante que sea, un forbucle indexado simple es más eficaz que sus alternativas.

Extracción de una propiedad individual de una matriz de 100000 elementos (a través de jsPerf)

Tradicional para loop 368 Ops / sec

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

ES6 para ... de bucle 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 () es lento, pero siéntase libre de usarlo si cree que la legibilidad vale más que el rendimiento.

Edición n. ° 2: 6/2019: enlace jsPerf roto, eliminado.

pscl
fuente
1
Llámame como quieras, pero el rendimiento cuenta. Si puede comprender el mapa, puede comprender un bucle for.
illcrx
Si bien es útil conocer los resultados de su evaluación comparativa, creo que es inapropiado aquí, ya que no responde a esta pregunta. Para mejorarlo, agregue también una respuesta real o comprímalo en un comentario (si es posible).
Ifedi Okonkwo
16

Es mejor usar algún tipo de bibliotecas como lodash o guión bajo para asegurar el navegador cruzado.

En Lodash puede obtener valores de una propiedad en una matriz siguiendo el método

_.map(objArray,"foo")

y en subrayado

_.pluck(objArray,"foo")

Ambos volverán

[1, 2, 3]
Tejas Patel
fuente
15

Utilizando Array.prototype.map:

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

Vea el enlace de arriba para ver una cuña para los navegadores anteriores a ES5.

Alnitak
fuente
10

En ES6, puedes hacer:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
Yuxuan Chen
fuente
¿Qué pasa si foo no está definido? Una serie de indefinidos no es bueno.
Godhar
6

Si bien mapes una solución adecuada para seleccionar 'columnas' de una lista de objetos, tiene un inconveniente. Si no se verifica explícitamente si las columnas existen o no, arrojará un error y (en el mejor de los casos) le proporcionará undefined. Optaría por una reducesolución, que simplemente puede ignorar la propiedad o incluso configurarlo con un valor predeterminado.

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

ejemplo de jsbin

Esto funcionaría incluso si uno de los elementos de la lista proporcionada no es un objeto o no contiene el campo.

Incluso puede hacerse más flexible negociando un valor predeterminado si un elemento no es un objeto o no contiene el 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;
    }, []);
}

ejemplo de jsbin

Esto sería lo mismo con el mapa, ya que la longitud de la matriz devuelta sería la misma que la matriz proporcionada. (En cuyo caso a mapes un poco más 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;
    }, []);
}

ejemplo de jsbin

Y luego está la solución más flexible, una que le permite cambiar entre ambos comportamientos simplemente proporcionando un 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;
    }, []);
}

ejemplo de jsbin

Como los ejemplos anteriores (con suerte) arrojan algo de luz sobre cómo funciona esto, acortemos un poco la Array.concatfunción utilizando la función.

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

ejemplo de jsbin

Rogier Spieker
fuente
6

En general, si desea extrapolar los valores de los objetos que están dentro de una matriz (como se describe en la pregunta), puede utilizar la reducción, el mapeo y la desestructuración de la 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)

El equivalente para for in loop sería:

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

Salida producida: ["palabra", "otra vez", "algo", "1", "2", "3"]

Eugen Sunic
fuente
3

Depende de su definición de "mejor".

Las otras respuestas señalan el uso del mapa, que es natural (especialmente para los hombres acostumbrados al estilo funcional) y conciso. Recomiendo encarecidamente usarlo (si no te molestas con los pocos chicos de IE8-IT). Entonces, si "mejor" significa "más conciso", "mantenible", "comprensible", entonces sí, es mucho mejor.

Por otro lado, esta belleza no viene sin costos adicionales. No soy un gran admirador del microbench, pero he realizado una pequeña prueba aquí . El resultado es predecible, la antigua forma fea parece ser más rápida que la función de mapa. Entonces, si "mejor" significa "más rápido", entonces no, quédese con la moda de la vieja escuela.

Nuevamente, esto es solo un microbench y de ninguna manera aboga contra el uso de map, solo son mis dos centavos :)

Sitifensys
fuente
Bueno, esto es en realidad del lado del servidor, no en un navegador, por lo que IE8 es irrelevante. Sí, mapes el camino obvio a seguir, de alguna manera no pude encontrarlo con Google (encontré solo el mapa de jquery, que no es relevante).
hyde
3

Si también desea admitir objetos tipo matriz, use Array.from (ES2015):

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

La ventaja que tiene sobre el método Array.prototype.map () es que la entrada también puede ser un conjunto :

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

Si desea valores múltiples en ES6 +, lo siguiente 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 }))

Esto funciona {foo, baz}de la izquierda está utilizando destructoring objeto y en el lado derecho de la flecha es equivalente a {foo: foo, baz: baz}Debido a objetos literales mejoradas de ES6 .

Chris Magnuson
fuente
justo lo que necesitaba, ¿qué tan rápido / lento crees que es esta solución?
Ruben
1
@Ruben Para decir realmente, creo que necesitarías tomar varias opciones de esta página y ponerlas a prueba usando algo como jsperf.com
Chris Magnuson
1

El mapa de funciones es una buena opción cuando se trata de matrices de objetos. Aunque ya se han publicado una serie de buenas respuestas, el ejemplo del uso del mapa con combinación con filtro podría ser útil.

En caso de que desee excluir las propiedades cuyos valores no están definidos o excluir solo una propiedad específica, puede hacer lo siguiente:

    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
fuente
0

La respuesta proporcionada anteriormente es buena para extraer una sola propiedad, y si desea extraer más de una propiedad de una matriz de objetos. ¡Aquí está la solución! En ese caso, simplemente podemos usar _.pick (objeto, [rutas])

_.pick(object, [paths])

Supongamos que objArray tiene objetos con tres propiedades como a continuación

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

Ahora queremos extraer la propiedad foo y bar de cada objeto y almacenarlos en una matriz separada. Primero iteraremos los elementos de la matriz utilizando el mapa y luego aplicaremos el método Lodash Library Standard _.pick ().

Ahora podemos extraer la propiedad 'foo' y 'bar'.

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

y el resultado sería [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

¡¡¡disfrutar!!!

Akash Jain
fuente
1
De donde _.pickviene No es una función estándar.
neves
Acabo de actualizar la respuesta, _.pick () es un método estándar de la biblioteca Lodash.
Akash Jain
0

Si tiene matrices anidadas, puede hacer que funcione así:

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
fuente