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 .


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.


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


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}

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}

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 .


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.

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}


12

versão es2015:

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

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

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

é 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 .


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

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

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

É 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),{});


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


11
Por favor, por favor, não recomendo estender Array.prototype!
jmar777

Ahh, entendi. Inicialmente eu pensei que esta era uma resposta proposta :)
jmar777
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.