javascript - recorrer - Desde una matriz de objetos, extraiga el valor de una propiedad como matriz
object.values javascript (12)
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 una matriz que contenga los valores de key foo
, dando como resultado un valor de [ 1, 3, 5 ]
.
Lo he logrado con el enfoque trivial, como sigue:
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 ]
¿Hay un método más elegante y de mayor rendimiento para lograr?
Nota sobre el duplicado sugerido , esa pregunta pregunta cómo convertir un objeto en una matriz, esta pregunta pregunta cómo extraer una propiedad única de una matriz de objetos.
Aquí hay una manera más corta de lograrlo:
let result = objArray.map(a => a.foo);
Compruebe la función _.pluck()
de _.pluck()
o la función _.pluck()
del _.pluck()
. Ambos hacen exactamente lo que quieres en una sola llamada de función!
var result = objArray.map(a => a.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 el guión bajo .
Actualización 2: Como señala Mark en los comentarios , en algún lugar entre Lodash v4 y 4.3, se ha agregado una nueva función que proporciona 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 que se pase una cadena como segundo parámetro, que se pasa a _.property()
. Como resultado, las siguientes dos líneas son equivalentes al ejemplo de código anterior de pre-Lodash 4.
var result = _.pluck(objArray, ''foo'');
_.property()
, y por _.map()
tanto _.map()
, también le permiten proporcionar una cadena o matriz separada por puntos para acceder a las sub-propiedades:
var result = _.map(objArray, ''foo'');
var result = _.map(objArray, _.property(''foo''));
Ambas llamadas _.map()
en el ejemplo anterior devolverán [5, 2, 9]
.
Si te interesa un poco más la programación funcional, echa un vistazo a la función R.pluck()
de R.pluck()
, que se vería así:
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'']));
Depende de tu definición de "mejor".
Las otras respuestas señalan el uso del mapa, que es natural (especialmente para los que están acostumbrados al estilo funcional) y conciso. Recomiendo encarecidamente usarlo (si no te molestas con los pocos tipos 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 costes adicionales. No soy un gran fan de microbench, pero he hecho una pequeña prueba aquí . El resultado es predecible, la vieja 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édate con la moda de la vieja escuela.
Nuevamente, esto es solo una microplaca y de ninguna manera abogar contra el uso del map
, son solo mis dos centavos :)
El mapa de funciones es una buena opción cuando se trata de matrices de objetos. Aunque ya se han publicado varias respuestas correctas, el ejemplo de uso de un 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:
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 : []));
}, []);
}
En ES6, puedes hacer:
for (let i in a) {
let temp = [];
for (let j in a[i]) {
temp.push(a[i][j]);
}
array.push(temp);
}
En general, si desea extrapolar los valores de los objetos que se encuentran dentro de una matriz (como se describe en la pregunta), entonces podría usar la reducción, el mapeo y la destrucción de matrices.
ES6
let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);
El uso equivalente de for loop sería el siguiente:
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)
Salida producida: ["word", "again", "some", "1", "2", "3"]
Es mejor usar algún tipo de bibliotecas como lodash o guión bajo para la seguridad en varios navegadores.
En lodash puede obtener los valores de una propiedad en matriz siguiendo el método
function getFields(input, field) {
return input.map(function(o) {
return o[field];
});
}
y en subrayado
_.map(objArray,"foo")
ambos volverán [1, 3, 5]
Hablando de las soluciones únicas de JS, he encontrado que, por poco elegante que sea, un simple bucle indexado es más eficaz que sus alternativas.
https://jsperf.com/extract-prop-from-object-array/
Extraer propiedad única de una matriz de 100000 elementos
Tradicional para loop 368 Ops / sec.
var result = R.pluck(''foo'')(objArray); // or just R.pluck(''foo'', objArray)
ES6 para ... de bucle 303 Ops / sec
var vals=[];
for(var i=0;i<testArray.length;i++){
vals.push(testArray[i].val);
}
Array.prototype.map 19 Ops / sec
var vals=[];
for(var item of testArray){
vals.push(item.val);
}
Edición: Ops / s actualizado 10/2017. TL; DR - .map () es lento. Pero a veces la legibilidad vale más que el rendimiento.
Sí, pero se basa en una característica ES5 de JavaScript. Esto significa que no funcionará en IE8 o anterior.
let result = objArray.map(a => a.foo);
En los intérpretes JS compatibles con ES6 puede usar una función de flecha para abreviar:
var result = objArray.map(function(a) {return a.foo;});
Si bien el map
es una solución adecuada para seleccionar "columnas" de una lista de objetos, tiene un inconveniente. Si no se comprueba explícitamente si las columnas existen o no, generará un error y (en el mejor de los casos) le proporcionará un valor undefined
. Optaría por una solución de reduce
, que puede simplemente ignorar la propiedad o incluso configurar un valor predeterminado.
_.pluck(objArray,"foo")
Esto funcionaría incluso si uno de los elementos de la lista provista no es un objeto o no contiene el campo.
Incluso se puede hacer más flexible al negociar un valor predeterminado si un artículo no es un objeto o no contiene el campo.
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;
}, []);
}
Esto sería lo mismo con map, ya que la longitud de la matriz devuelta sería la misma que la matriz proporcionada. (En cuyo caso un map
es un poco más barato que una reduce
):
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;
}, []);
}
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) {
// 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;
}, []);
}
Como los ejemplos anteriores (con suerte) arrojan algo de luz sobre la forma en que funciona esto, Array.concat
un poco la función utilizando la función Array.concat
.
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;
}, []);
}
Si también desea admitir objetos de tipo matriz, use Array.from (ES2015):
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]});
La ventaja que tiene sobre el método Array.prototype.map () es que la entrada también puede ser un conjunto :
Array.from(arrayLike, x => x.foo);
Utilizando Documentation :
var vals = testArray.map(function(a) {return a.val;});
Vea el enlace de arriba para un shim para los navegadores pre-ES5.