Convertir le tableau d'objets en carte de hachage, indexé par une valeur d'attribut de l'objet

305

Cas d'utilisation

Le cas d'utilisation consiste à convertir un tableau d'objets en une carte de hachage basée sur une chaîne ou une fonction fournie pour évaluer et utiliser comme clé dans la carte de hachage et la valeur en tant qu'objet lui-même. Un cas courant d'utilisation est la conversion d'un tableau d'objets en une carte de hachage d'objets.

Code

Voici un petit extrait en JavaScript pour convertir un tableau d'objets en une carte de hachage, indexé par la valeur d'attribut de l'objet. Vous pouvez fournir une fonction pour évaluer dynamiquement la clé de la carte de hachage (au moment de l'exécution). J'espère que cela aidera quelqu'un à l'avenir.

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

Vous pouvez trouver l'essentiel ici: Convertit le tableau d'objets en HashMap .

Naveen I
la source
Vous pouvez utiliser la carte JavaScript au lieu de l'objet. Consultez stackoverflow.com/a/54246603/5042169
juin

Réponses:

471

C'est assez banal à voir avec 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' }

Remarque: Array.prototype.reduce() est IE9 +, donc si vous devez prendre en charge des navigateurs plus anciens, vous devrez le remplir.

jmar777
la source
47
result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});Pour les fans d'une ligne ES6: D
Teodor Sandu
31
Pour @Mtz ES6 ventilateurs d' une ligne, la réponse mateuscb ci - dessous est beaucoup plus petit et plus propre: result = new Map(arr.map(obj => [obj.key, obj.val]));. Plus important encore, il est très clair qu'une carte est retournée.
Ryan Shillington
2
@RyanShillington nous sommes ici dans un contexte de réponse, Array.prototype.reducetel que proposé par jmar777. Mapest en effet plus court mais c'est autre chose. Je me conformais à l'intention initiale. N'oubliez pas que ce n'est pas un forum, vous voudrez peut-être en savoir plus sur la structure SO Q / A.
Teodor Sandu
2
@Mtz Assez juste.
Ryan Shillington
1
Ce n'est pas ce qui a été demandé, à mon humble avis. Le résultat correct pour le tableau serait montré: { "foo": {key: 'foo', val: 'bar'}, "hello": {key: 'hello', val: 'world'} }. Notez que chaque élément d'origine doit être conservé dans son intégralité . Ou en utilisant les données du Q: {"345": {id:345, name:"kumar"}, ...}. CORRECTIF: changer le code pour êtremap[obj.key] = obj;
ToolmakerSteve
302

En utilisant ES6 Map ( assez bien pris en charge ), vous pouvez essayer ceci:

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
la source
4
Il est également important de noter que pour tirer quelque chose d'un, Mapvous devez utiliser result.get(keyName)plutôt que juste result[keyName]. Notez également que n'importe quel objet peut être utilisé comme clé et pas seulement comme chaîne.
Simon_Weaver
5
Une autre version de TypeScript ressemblerait à ceci: var result = new Map(arr.map(i => [i.key, i.val] as [string, string]));que certains pourraient trouver plus facile à comprendre. as [string, string]Ajout d'un cast de type note .
AlexV
Lorsque j'exécute ce code dans Chrome v71, je reçois toujours un tableau:Result is: [["foo","bar"],["hello","world"]]
Jean-François Beauchamp
PS resultn'est pas un hachage comme demandé par l'OP.
Jean-François Beauchamp
1
Une autre version dactylographiée:var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
Aziz Javed
39

Avec lodash , cela peut être fait en utilisant 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}
éclateur
la source
Ce n'est pas un hashmap
Pomme De Terre
37

Utilisation d'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
la source
2
Parfait, juste ce dont j'avais besoin;)
Pierre
1
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value}))));a dû faire ce changement pour travailler avec tapuscrit.
ruwan800
24

Utilisation de l'opérateur d'étalement:

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

Démonstration de l'extrait de code sur jsFiddle .

Pedro Lopes
la source
7
Je suis exactement là à cause de ça! Comment l'opérateur de propagation fonctionne-t-il contre l'ancienne manière habituelle d'attribuer simplement la nouvelle clé et de retourner l'accumulateur? car il crée une nouvelle copie à chaque fois, alors la propagation se comportera mal !
AMTourky
1
maintenant vous vous étalez à chaque itération. Il devrait être sûr de muter dans le réducteur. `` `const result = arr.reduce ((accumulator, target) => {accumulator [target.key]: target.val; return accumulator}, {}); ``
MTJ
17

Vous pouvez utiliser Array.prototype.reduce () et la carte JavaScript réelle à la place juste un objet 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);

Quelle est la différence entre Map And Object?
Auparavant, avant l'implémentation de Map en JavaScript, Object était utilisé comme Map en raison de leur structure similaire.
Selon votre cas d'utilisation, si vous devez avoir commandé des clés, avoir besoin d'accéder à la taille de la carte ou avoir des ajouts et des suppressions fréquents de la carte, une carte est préférable.

Citation du document MDN : les
objets sont similaires aux cartes dans la mesure où ils vous permettent de définir des clés pour des valeurs, de récupérer ces valeurs, de supprimer des clés et de détecter si quelque chose est stocké sur une clé. Pour cette raison (et parce qu'il n'y avait pas d'alternatives intégrées), les objets ont été utilisés comme cartes historiquement; cependant, il existe des différences importantes qui rendent préférable l'utilisation d'une carte dans certains cas:

  • Les clés d'un objet sont des chaînes et des symboles, alors qu'elles peuvent être n'importe quelle valeur pour une carte, y compris les fonctions, les objets et toute primitive.
  • Les clés de la carte sont ordonnées tandis que les clés ajoutées à l'objet ne le sont pas. Ainsi, lors de son itération, un objet Map renvoie les clés par ordre d'insertion.
  • Vous pouvez facilement obtenir la taille d'une carte avec la propriété size, tandis que le nombre de propriétés dans un objet doit être déterminé manuellement.
  • Une carte est un itérable et peut donc être directement itéré, alors que l'itération sur un objet nécessite d'obtenir ses clés d'une certaine manière et de les itérer.
  • Un objet a un prototype, il y a donc des clés par défaut dans la carte qui pourraient entrer en collision avec vos clés si vous ne faites pas attention. Depuis ES5, cela peut être contourné en utilisant map = Object.create (null), mais cela est rarement fait.
  • Une carte peut mieux fonctionner dans les scénarios impliquant l'ajout et la suppression fréquents de paires de clés.
Juin711
la source
1
Il vous manque une flèche. Changement (mapAccumulator, obj) {...}pour(mapAccumulator, obj) => {...}
Mayid
15

Vous pouvez utiliser la nouvelle Object.fromEntries()méthode.

Exemple:

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
la source
12

Version es2015:

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

C'est ce que je fais en TypeScript j'ai une petite bibliothèque d'utils où je mets des choses comme ça

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

usage:

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

ou si vous avez un identifiant autre que 'id'

const hash = arrayToHash([{key:1,data:'data'},{key:2,data:'data'}], 'key')
Peter
la source
Si vous souhaitez utiliser un objet comme clé, vous devrez utiliser Map au lieu d'Object car Dactylographe ne vous permettra pas d'utiliser des objets comme clés
Dany Dhondt
3

Il existe de meilleures façons de le faire, comme expliqué par d'autres affiches. Mais si je veux m'en tenir à JS pur et à l'ancienne, alors voici:

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
la source
est-il conseillé d'utiliser la méthode de réduction que cette méthode. J'ai envie d'utiliser cette méthode. c'est simple et facile de tout voir.
Santhosh Yedidi
J'adore cette approche. Je pense que parfois le code le plus simple est le meilleur. Les gens sont désactivés par la mutabilité de nos jours, mais tant qu'elle est contenue, la mutabilité est en fait assez impressionnante et performante.
Luis Aceituno
3

Si vous voulez convertir à la nouvelle ES6 Map faire:

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

Pourquoi devriez-vous utiliser ce type de carte? Cela dépend de vous. Jetez un oeil à cela .

Tiago Bértolo
la source
2

Utilisation de 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
la source
3
n'utilise pas .map (...) inutile dans cet exemple car vous n'y retournez rien? Je suggérerais pour chacun dans ce cas.
cuddlecheek
2

Avec 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
la source
1

Une petite amélioration sur l' reduceutilisation:

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
la source
Est-ce plus rapide que l' autre réponse?
orad du
@orad probablement pas car il répand l'accumulateur et crée un nouvel objet à chaque itération.
Luckylooke
1

essayer

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

Kamil Kiełczewski
la source
0

Voici un petit extrait que j'ai créé en javascript pour convertir un tableau d'objets en carte de hachage, indexé par la valeur d'attribut de l'objet. Vous pouvez fournir une fonction pour évaluer dynamiquement la clé de la carte de hachage (au moment de l'exécution).

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

Vous pouvez trouver l'essentiel ici: https://gist.github.com/naveen-ithappu/c7cd5026f6002131c1fa

Naveen I
la source
11
S'il vous plaît, s'il vous plaît, veuillez ne pas recommander l'extension Array.prototype!
jmar777
Ahh je vois. J'ai d'abord pensé que c'était une réponse proposée :)
jmar777