Converter matriz de objetos em mapa de hash, indexado por um valor de atributo do objeto

305

Caso de Uso

O caso de uso é converter uma matriz de objetos em um mapa de hash com base na cadeia ou função fornecida para avaliar e usar como chave no mapa de hash e valor como um objeto em si. Um caso comum de usar isso é converter uma matriz de objetos em um mapa hash de objetos.

Código

A seguir, um pequeno trecho em JavaScript para converter uma matriz de objetos em um mapa de hash, indexado pelo valor do atributo do objeto. Você pode fornecer uma função para avaliar a chave do mapa de hash dinamicamente (tempo de execução). Espero que isso ajude alguém no 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;
};

Você pode encontrar a essência aqui: Converte a matriz de objetos em HashMap .

Naveen I
fonte
Você pode usar o mapa JavaScript em vez do objeto. Verifique stackoverflow.com/a/54246603/5042169
Jun711 18/01/19

Respostas:

471

Isso é bastante trivial relacionado a 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() é o IE9 +, portanto, se você precisar dar suporte a navegadores mais antigos, precisará preenchê-lo novamente.

jmar777
fonte
47
result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});Para os fãs de uma linha do ES6: D
Teodor Sandu 31/01
31
@Mtz Para ES6 fãs de uma linha, a resposta de mateuscb abaixo é muito menor e mais limpo: result = new Map(arr.map(obj => [obj.key, obj.val]));. Mais importante, torna super claro que um mapa está sendo retornado.
Ryan Shillington
2
@RyanShillington, estamos no contexto de uma resposta aqui, Array.prototype.reduceproposta por jmar777. Mapé realmente mais curto, mas é uma coisa diferente. Eu estava mantendo a linha com a intenção original. Lembre-se de que este não é um fórum, você pode querer ler mais sobre a estrutura SO Q / A.
Teodor Sandu
2
@Mtz Fair suficiente.
Ryan Shillington
1
Não foi isso que foi pedido, IMHO. O resultado correcto para a matriz mostrada seria: { "foo": {key: 'foo', val: 'bar'}, "hello": {key: 'hello', val: 'world'} }. Observe que cada elemento original deve ser mantido em sua totalidade . Ou usando dados do Q: {"345": {id:345, name:"kumar"}, ...}. CORRECÇÃO: Alterar o código para sermap[obj.key] = obj;
ToolmakerSteve
302

Usando o ES6 Map ( muito bem suportado ), você pode tentar o seguinte:

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
fonte
4
Também é importante notar que, para obter algo de um, Mapvocê precisa usar, result.get(keyName)em vez de apenas result[keyName]. Observe também que qualquer objeto pode ser usado como chave e não apenas como uma string.
Simon_Weaver
5
Outra versão do TypeScript seria assim: var result = new Map(arr.map(i => [i.key, i.val] as [string, string]));que alguns podem achar mais fácil de entender. Nota do as [string, string]tipo de conversão adicionada.
AlexV #
Quando executo esse código no Chrome v71, ainda estou recebendo uma matriz:Result is: [["foo","bar"],["hello","world"]]
Jean-François Beauchamp
O PS resultnão é um hash, conforme solicitado pelo OP.
Jean-François Beauchamp
1
Outra versão datilografada:var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
Aziz Javed
39

Com o lodash , isso pode ser feito 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}
splintor
fonte
Isso não é um hashmap
Pomme De Terre
37

Usando o spread ES6 + 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
fonte
2
Perfeito, apenas o que eu precisava;)
Pierre
1
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value}))));teve que fazer essa alteração para trabalhar com texto datilografado.
precisa saber é o seguinte
24

Usando o operador spread:

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

Demonstração do snippet de código no jsFiddle .

Pedro Lopes
fonte
7
Estou exatamente aqui por causa disso! como o operador de propagação executa novamente a maneira antiga regular de atribuir a nova chave e retornar o acumulador? como está criando uma nova cópia a cada vez, a propagação terá um desempenho ruim !
AMTourky
1
agora você se espalha em todas as iterações. Deve ser seguro fazer a mutação no redutor. `` `const result = arr.reduce ((acumulador, alvo) => {acumulador [target.key]: target.val; return acumulator}, {}); ``
MTJ 31/01
17

Você pode usar Array.prototype.reduce () e o Mapa JavaScript real em vez de apenas um Objeto 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);

O que é diferente entre Mapa e Objeto?
Antes, antes de o Map ser implementado no JavaScript, o Object era usado como um mapa por causa de sua estrutura semelhante.
Dependendo do seu caso de uso, se você precisar ter chaves solicitadas, precisar acessar o tamanho do mapa ou ter a adição e remoção frequentes do mapa, é preferível um Mapa.

Citação do documento MDN : Os
objetos são semelhantes aos Mapas, pois ambos permitem definir chaves para valores, recuperar esses valores, excluir chaves e detectar se algo está armazenado em uma chave. Por causa disso (e porque não havia alternativas internas), os Objetos têm sido usados ​​como Mapas historicamente; no entanto, existem diferenças importantes que tornam o uso de um mapa preferível em certos casos:

  • As chaves de um Objeto são Strings e Símbolos, enquanto que podem ter qualquer valor para um Mapa, incluindo funções, objetos e qualquer primitivo.
  • As chaves no mapa são ordenadas, enquanto as chaves adicionadas ao objeto não são. Assim, ao iterar sobre ele, um objeto Map retorna chaves em ordem de inserção.
  • Você pode obter facilmente o tamanho de um mapa com a propriedade size, enquanto o número de propriedades em um objeto deve ser determinado manualmente.
  • Um mapa é iterável e, portanto, pode ser iterado diretamente, enquanto a iteração sobre um objeto exige a obtenção de suas chaves de alguma maneira e a iteração sobre elas.
  • Um objeto tem um protótipo; portanto, existem chaves padrão no mapa que podem colidir com suas chaves se você não tomar cuidado. No ES5, isso pode ser ignorado usando map = Object.create (null), mas isso raramente é feito.
  • Um mapa pode ter um desempenho melhor em cenários que envolvem adição e remoção freqüentes de pares de chaves.
Jun711
fonte
1
Está faltando uma flecha. Alterar (mapAccumulator, obj) {...}para(mapAccumulator, obj) => {...}
maio
15

Você pode usar o novo Object.fromEntries() método.

Exemplo:

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
fonte
12

versão es2015:

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

Isto é o que estou fazendo no TypeScript. Tenho uma pequena biblioteca de utilitários onde coloco coisas 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'}])

ou se você tiver um identificador diferente de 'id'

const hash = arrayToHash([{key:1,data:'data'},{key:2,data:'data'}], 'key')
Peter
fonte
Se você quiser usar um objeto como uma chave, você terá que usar Mapa em vez de objeto porque typescript não vai deixar você usar objetos como chaves
Dany Dhondt
3

Existem maneiras melhores de fazer isso, conforme explicado por outros pôsteres. Mas se eu quero me ater ao JS puro e à moda antiga, aqui 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
fonte
é aconselhável usar o método de redução que esse método. Eu sinto vontade de usar esse método. é simples e fácil de ver tudo.
Santhosh Yedidi
Eu amo essa abordagem. Às vezes acho que o código mais simples é o melhor. Hoje em dia, as pessoas estão desabilitadas pela mutabilidade, mas, desde que contidas, a mutabilidade é realmente impressionante e de alto desempenho.
Luis Aceituno
3

Se você deseja converter para o novo mapa ES6, faça o seguinte:

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

Por que você deve usar esse tipo de mapa? Bem, isso é com você. Dê uma olhada nisso .

Tiago Bértolo
fonte
2

Usando Javascript simples

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
fonte
3
não está usando .map (...) sem sentido neste exemplo, pois você não retorna nada nele? Eu sugeriria forEach neste caso.
precisa saber é o seguinte
2

Com 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' }
Tho
fonte
1

Uma pequena melhoria no 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
fonte
É mais rápido que a outra resposta?
Orad
@orad provavelmente não, pois espalha o acumulador e cria novo objeto a cada iteração.
Luckylooke 22/05/19
1

experimentar

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

Kamil Kiełczewski
fonte
0

A seguir, um pequeno trecho que eu criei em javascript para converter a matriz de objetos em mapa de hash, indexada pelo valor do atributo do objeto. Você pode fornecer uma função para avaliar a chave do mapa de hash dinamicamente (tempo de execução).

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

Você pode encontrar a essência aqui: https://gist.github.com/naveen-ithappu/c7cd5026f6002131c1fa

Naveen I
fonte
11
Por favor, por favor, não recomendo estender Array.prototype!
jmar777
Ahh, entendi. Inicialmente eu pensei que esta era uma resposta proposta :)
jmar777