Convierta la matriz de objetos en un mapa hash, indexado por un valor de atributo del objeto

305

Caso de uso

El caso de uso es convertir una matriz de objetos en un mapa hash basado en una cadena o función proporcionada para evaluar y usar como la clave en el mapa hash y el valor como un objeto en sí mismo. Un caso común de usar esto es convertir una matriz de objetos en un mapa hash de objetos.

Código

El siguiente es un pequeño fragmento en JavaScript para convertir una matriz de objetos en un mapa hash, indexado por el valor del atributo del objeto. Puede proporcionar una función para evaluar la clave del mapa hash dinámicamente (tiempo de ejecución). Espero que esto ayude a alguien en el futuro.

function isFunction(func) {
    return Object.prototype.toString.call(func) === '[object Function]';
}

/**
 * This function converts an array to hash map
 * @param {String | function} key describes the key to be evaluated in each object to use as key for hashmap
 * @returns Object
 * @Example 
 *      [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap("id")
 *      Returns :- Object {123: Object, 345: Object}
 *
 *      [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap(function(obj){return obj.id+1})
 *      Returns :- Object {124: Object, 346: Object}
 */
Array.prototype.toHashMap = function(key) {
    var _hashMap = {}, getKey = isFunction(key)?key: function(_obj){return _obj[key];};
    this.forEach(function (obj){
        _hashMap[getKey(obj)] = obj;
    });
    return _hashMap;
};

Puede encontrar la esencia aquí: Convierte una matriz de objetos en HashMap .

Naveen I
fuente
Puede usar JavaScript Map en lugar de Object. Echa un vistazo a stackoverflow.com/a/54246603/5042169
jun711

Respuestas:

471

Esto es bastante trivial que ver con Array.prototype.reduce:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = arr.reduce(function(map, obj) {
    map[obj.key] = obj.val;
    return map;
}, {});

console.log(result);
// { foo:'bar', hello:'world' }

Nota: Array.prototype.reduce() es IE9 +, por lo que si necesita admitir navegadores antiguos, deberá rellenarlo.

jmar777
fuente
47
result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});Para los fanáticos de ES6 one-liner: D
Teodor Sandu el
31
Para @Mtz ES6 ventiladores de una sola línea, la respuesta de mateuscb a continuación es mucho más pequeño y más limpio: result = new Map(arr.map(obj => [obj.key, obj.val]));. Lo más importante, deja muy claro que se está devolviendo un mapa.
Ryan Shillington
2
@ RyanShillington estamos en el contexto de una respuesta aquí, que es Array.prototype.reducecomo lo propuso jmar777. Mapes de hecho más corto pero es una cosa diferente. Me mantenía en línea con la intención original. Recuerde que este no es un foro, es posible que desee leer más sobre la estructura SO Q / A.
Teodor Sandu
2
@Mtz justo lo suficiente.
Ryan Shillington
1
Esto no es lo que se pidió, en mi humilde opinión. El resultado correcto para la matriz de muestra sería: { "foo": {key: 'foo', val: 'bar'}, "hello": {key: 'hello', val: 'world'} }. Tenga en cuenta que cada elemento original debe mantenerse en su totalidad . O el uso de los datos del Q: {"345": {id:345, name:"kumar"}, ...}. REVISIÓN: Cambiar el código para sermap[obj.key] = obj;
ToolmakerSteve
302

Usando ES6 Map ( bastante bien soportado ), puedes probar esto:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = new Map(arr.map(i => [i.key, i.val]));

// When using TypeScript, need to specify type:
// var result = arr.map((i): [string, string] => [i.key, i.val])

// Unfortunately maps don't stringify well.  This is the contents in array form.
console.log("Result is: " + JSON.stringify([...result])); 
// Map {"foo" => "bar", "hello" => "world"}

mateuscb
fuente
44
También es importante tener en cuenta que para sacar algo de una Mapnecesita usar result.get(keyName)en lugar de solo result[keyName]. También tenga en cuenta que cualquier objeto puede usarse como clave y no solo como una cadena.
Simon_Weaver
55
Otra versión de TypeScript se vería así: var result = new Map(arr.map(i => [i.key, i.val] as [string, string]));que algunos podrían encontrar más fácil de entender. Nota as [string, string]tipo elenco añadido.
AlexV
Cuando ejecuto este código en Chrome v71, sigo obteniendo una matriz:Result is: [["foo","bar"],["hello","world"]]
Jean-François Beauchamp
PS resultno es un hash según lo solicitado por el OP.
Jean-François Beauchamp
1
Otra versión mecanografiada:var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
Aziz Javed el
39

Con lodash , esto se puede hacer usando keyBy :

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = _.keyBy(arr, o => o.key);

console.log(result);
// Object {foo: Object, hello: Object}
férula
fuente
Eso no es un hashmap
Pomme De Terre
37

Usando ES6 spread + Object.assign:

array = [{key: 'a', value: 'b', redundant: 'aaa'}, {key: 'x', value: 'y', redundant: 'zzz'}]

const hash = Object.assign({}, ...array.map(s => ({[s.key]: s.value})));

console.log(hash) // {a: b, x: y}
shuk
fuente
2
Perfecto, justo lo que necesitaba;)
Pierre
1
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value}))));tuvo que hacer este cambio para trabajar con mecanografiado.
ruwan800
24

Usando el operador de propagación:

const result = arr.reduce(
    (accumulator, target) => ({ ...accumulator, [target.key]: target.val }),
    {});

Demostración del fragmento de código en jsFiddle .

Pedro Lopes
fuente
77
¡Estoy exactamente aquí por esto! ¿Cómo se realiza el operador de propagación contra la antigua forma habitual de simplemente asignar la nueva clave y devolver el acumulador? ya que está creando una nueva copia cada vez, ¡entonces la difusión funcionará mal !
AMTourky
1
ahora te extiendes en cada iteración. Debe ser seguro mutar en el reductor. `` `resultado const = arr.reduce ((acumulador, objetivo) => {acumulador [target.key]: target.val; return acumulator}, {}); ``
MTJ
17

Puede usar Array.prototype.reduce () y el Mapa de JavaScript real en lugar de solo un Objeto de JavaScript .

let keyValueObjArray = [
  { key: 'key1', val: 'val1' },
  { key: 'key2', val: 'val2' },
  { key: 'key3', val: 'val3' }
];

let keyValueMap = keyValueObjArray.reduce((mapAccumulator, obj) => {
  // either one of the following syntax works
  // mapAccumulator[obj.key] = obj.val;
  mapAccumulator.set(obj.key, obj.val);

  return mapAccumulator;
}, new Map());

console.log(keyValueMap);
console.log(keyValueMap.size);

¿Qué es diferente entre mapa y objeto?
Anteriormente, antes de que Map se implementara en JavaScript, Object se usaba como Map debido a su estructura similar.
Dependiendo de su caso de uso, si necesita tener claves ordenadas, necesita acceder al tamaño del mapa o tener que agregarlo y eliminarlo con frecuencia, es preferible un Mapa.

Cita del documento MDN : los
objetos son similares a Maps en que ambos le permiten establecer claves en valores, recuperar esos valores, eliminar claves y detectar si algo está almacenado en una clave. Debido a esto (y porque no había alternativas incorporadas), los Objetos se han utilizado como Mapas históricamente; sin embargo, existen diferencias importantes que hacen que el uso de un mapa sea preferible en ciertos casos:

  • Las claves de un objeto son cadenas y símbolos, mientras que pueden ser cualquier valor para un mapa, incluidas funciones, objetos y cualquier primitivo.
  • Las claves en el Mapa se ordenan mientras que las claves agregadas al objeto no. Por lo tanto, al iterar sobre él, un objeto Map devuelve claves en orden de inserción.
  • Puede obtener el tamaño de un Mapa fácilmente con la propiedad de tamaño, mientras que el número de propiedades en un Objeto debe determinarse manualmente.
  • Un mapa es iterable y, por lo tanto, puede iterarse directamente, mientras que iterar sobre un objeto requiere obtener sus claves de alguna manera e iterar sobre ellas.
  • Un objeto tiene un prototipo, por lo que hay claves predeterminadas en el mapa que podrían colisionar con sus claves si no tiene cuidado. A partir de ES5, esto se puede omitir usando map = Object.create (nulo), pero esto rara vez se hace.
  • Un mapa puede funcionar mejor en escenarios que implican la adición y eliminación frecuente de pares de claves.
Jun711
fuente
1
Te falta una flecha. Cambio (mapAccumulator, obj) {...}para(mapAccumulator, obj) => {...}
mayid
15

Puedes usar el nuevo Object.fromEntries() método.

Ejemplo:

const array = [
   {key: 'a', value: 'b', redundant: 'aaa'},
   {key: 'x', value: 'y', redundant: 'zzz'}
]

const hash = Object.fromEntries(
   array.map(e => [e.key, e.value])
)

console.log(hash) // {a: b, x: y}

Fabiano Taioli
fuente
12

Versión es2015:

const myMap = new Map(objArray.map(obj => [ obj.key, obj.val ]));
bario
fuente
4

Esto es lo que estoy haciendo en TypeScript. Tengo una pequeña biblioteca de utilidades donde pongo cosas como esta.

export const arrayToHash = (array: any[], id: string = 'id') => 
         array.reduce((obj, item) =>  (obj[item[id]] = item , obj), {})

uso:

const hash = arrayToHash([{id:1,data:'data'},{id:2,data:'data'}])

o si tiene un identificador que no sea 'id'

const hash = arrayToHash([{key:1,data:'data'},{key:2,data:'data'}], 'key')
Peter
fuente
Si desea usar un objeto como clave, tendrá que usar Map en lugar de Object porque el mecanografiado no le permitirá usar objetos como claves
Dany Dhondt
3

Hay mejores maneras de hacer esto como lo explican otros carteles. Pero si quiero apegarme a JS puro y a la antigua, entonces aquí está:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' },
    { key: 'hello', val: 'universe' }
];

var map = {};
for (var i = 0; i < arr.length; i++) {
    var key = arr[i].key;
    var value = arr[i].val;

    if (key in map) {
        map[key].push(value);
    } else {
        map[key] = [value];
    }
}

console.log(map);
rhel.user
fuente
¿Se recomienda utilizar un método reducido que este método? Tengo ganas de usar este método. Es simple y fácil de ver todo.
Santhosh Yedidi
Me encanta este enfoque. Creo que a veces el código más simple es el mejor. La mutabilidad desaprueba a las personas hoy en día, pero mientras esté contenida, la mutabilidad es realmente asombrosa y eficaz.
Luis Aceituno
3

Si desea convertir al nuevo mapa ES6, haga esto:

var kvArray = [['key1', 'value1'], ['key2', 'value2']];
var myMap = new Map(kvArray);

¿Por qué deberías usar este tipo de mapa? Bueno, eso depende de ti. Mira esto .

Tiago Bértolo
fuente
2

Usando Javascript simple

var createMapFromList = function(objectList, property) {
    var objMap = {};
    objectList.forEach(function(obj) {
      objMap[obj[property]] = obj;
    });
    return objMap;
  };
// objectList - the array  ;  property - property as the key
Partha Roy
fuente
3
no está usando .map (...) no tiene sentido en este ejemplo, ya que no devuelve nada en él? Sugeriría para cada uno en este caso.
cuddlecheek
2

Con lodash:

const items = [
    { key: 'foo', value: 'bar' },
    { key: 'hello', value: 'world' }
];

const map = _.fromPairs(items.map(item => [item.key, item.value]));

console.log(map); // { foo: 'bar', hello: 'world' }
aunque
fuente
1

Una pequeña mejora en el reduceuso:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = arr.reduce((map, obj) => ({
    ...map,
    [obj.key] = obj.val
}), {});

console.log(result);
// { foo: 'bar', hello: 'world' }
Mor Shemesh
fuente
¿Es más rápido que la otra respuesta?
orad
@orad probablemente no, ya que extiende el acumulador y crea un nuevo objeto en cada iteración.
Luckylooke
1

tratar

let toHashMap = (a,f) => a.reduce((a,c)=> (a[f(c)]=c,a),{});

Kamil Kiełczewski
fuente
0

El siguiente es un pequeño fragmento que he creado en JavaScript para convertir una matriz de objetos en un mapa hash, indexado por el valor del atributo del objeto. Puede proporcionar una función para evaluar la clave del mapa hash dinámicamente (tiempo de ejecución).

function isFunction(func){
    return Object.prototype.toString.call(func) === '[object Function]';
}

/**
 * This function converts an array to hash map
 * @param {String | function} key describes the key to be evaluated in each object to use as key for hasmap
 * @returns Object
 * @Example 
 *      [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap("id")
        Returns :- Object {123: Object, 345: Object}

        [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap(function(obj){return obj.id+1})
        Returns :- Object {124: Object, 346: Object}
 */
Array.prototype.toHashMap = function(key){
    var _hashMap = {}, getKey = isFunction(key)?key: function(_obj){return _obj[key];};
    this.forEach(function (obj){
        _hashMap[getKey(obj)] = obj;
    });
    return _hashMap;
};

Puede encontrar la esencia aquí: https://gist.github.com/naveen-ithappu/c7cd5026f6002131c1fa

Naveen I
fuente
11
¡Por favor, por favor, no recomiendo extender Array.prototype!
jmar777
Ahh ya veo. Inicialmente pensé que era una respuesta propuesta :)
jmar777