540

Use Case

The use case is to convert an array of objects into a hash map based on string or function provided to evaluate and use as the key in the hash map and value as an object itself. A common case of using this is converting an array of objects into a hash map of objects.

Code

The following is a small snippet in JavaScript to convert an array of objects to a hash map, indexed by the attribute value of object. You can provide a function to evaluate the key of hash map dynamically (run time).

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

You can find the gist here: Converts Array of Objects to HashMap.

Halo
  • 1,730
  • 1
  • 8
  • 31
Naveen I
  • 5,695
  • 2
  • 15
  • 8
  • You can use JavaScript Map instead of Object. Check out https://stackoverflow.com/a/54246603/5042169 – Jun Jan 18 '19 at 01:31

22 Answers22

715

This is fairly trivial to do with 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' }

Note: Array.prototype.reduce() is IE9+, so if you need to support older browsers you will need to polyfill it.

vsync
  • 118,978
  • 58
  • 307
  • 400
jmar777
  • 38,796
  • 11
  • 66
  • 64
  • Modified the usecase to avoid confusion. I tried to target scenario which is most often encountered by JS developers in view driven application. – Naveen I Oct 09 '14 at 09:36
  • 90
    `result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});` For ES6 one-liner fans :D – Teodor Sandu Jan 31 '18 at 14:44
  • 74
    @Mtz For ES6 one-line fans, mateuscb's response below is much smaller and cleaner: `result = new Map(arr.map(obj => [obj.key, obj.val]));`. Most importantly, it makes it super clear that a map is being returned. – Ryan Shillington Jun 07 '18 at 16:31
  • 6
    @RyanShillington we are in an answer's context here, which is `Array.prototype.reduce` as proposed by jmar777. `Map` is indeed shorter but it's a different thing. I was keeping in line with the original intent. Remember this is not a forum, you might want to read more about the SO Q/A structure. – Teodor Sandu Jun 08 '18 at 13:59
  • 2
    @Mtz Fair enough. – Ryan Shillington Jun 11 '18 at 19:33
  • 8
    This isn't what was asked for, IMHO. The correct result for the array shown would be: `{ "foo": {key: 'foo', val: 'bar'}, "hello": {key: 'hello', val: 'world'} }`. Note that each original element *should be kept in its entirety*. Or using the Q's data: `{"345": {id:345, name:"kumar"}, ...}`. FIX: Change code to be `map[obj.key] = obj;` – ToolmakerSteve Nov 03 '19 at 19:09
  • As coded, this will return an instance of Object, not map. For instance: `result.has('foo')` will fail as `has()` is not defined for Object. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map – Jacob Barnes Sep 23 '20 at 15:01
  • 1
    @JacobBarnes This question was from 2014, before `Map` was a thing in JavaScript. Back then, "hash map" ~= "an object that I will shove a bunch of arbitrary properties onto". – jmar777 Feb 05 '21 at 14:57
  • What about `result = arr.reduce((map, obj) => ({...map, [obj.key]: obj.val}), {})` – Fabiano Taioli Apr 22 '21 at 12:30
  • 1
    I find these reduce calls quite clunky. What's wrong with a plain loop? `const result = {}; for (const obj of arr) result[obj.key] = obj.val;` – laurent Jun 24 '21 at 19:30
492

Using ES6 Map (pretty well supported), you can try this:

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"}
Ryan Shillington
  • 23,006
  • 14
  • 93
  • 108
mateuscb
  • 10,150
  • 3
  • 52
  • 76
  • 18
    Also important to note that to get something out of a `Map` you need to use `result.get(keyName)` as opposed to just `result[keyName]`. Also note that any object can be used as the key and not just a string. – Simon_Weaver Jan 26 '18 at 08:57
  • 11
    Another **TypeScript** version would look like this: `var result = new Map(arr.map(i => [i.key, i.val] as [string, string]));` which some might find easier to understand. Note `as [string, string]` type cast added. – AlexV Nov 21 '18 at 22:12
  • When I run this code in Chrome v71, I am still getting an array: `Result is: [["foo","bar"],["hello","world"]]` – Jean-François Beauchamp Jan 17 '19 at 16:35
  • 1
    P.S. `result` is not a hash as requested by the OP. – Jean-François Beauchamp Jan 17 '19 at 16:42
  • 1
    Another typescript version: `var result = new Map(arr.map(i => [i.key, i.val]));` – azizj Oct 21 '19 at 20:22
  • Your code helpfull for me, thanks for share @mateuscb. – muratoner Nov 29 '19 at 07:34
  • I was about to comment that this didn't work in Typescript and then I saw the commented portion. LOL. Wish I could give you more than +1. – Emperor Eto Feb 28 '20 at 01:10
  • Passing an array to `Map` constructor turned out to be less performant than using `Map.set` in a loop, at least on this test: https://jsbench.me/6gkawrdlys/1 – rekans Jun 01 '20 at 17:23
  • AWS SDK returns `string | undefined` in a ton of places, so Typescript version won't work with anything but static examples, because `undefined` happens a lot in the real world. – Paul S Oct 17 '20 at 01:33
  • `var result = Object.fromEntries(new Map(arr.map(i => [i.key, i.val])))` if you want an Object instead of a `Map` – Casey Sep 17 '21 at 20:43
  • 1
    @AlexV that is a type assertion and should be avoided whenever possible. Instead you can type the Map using `new Map`. – Krisztián Balla Feb 06 '22 at 11:39
115

You can use the new Object.fromEntries() method.

Example:

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
  • 5,270
  • 1
  • 35
  • 49
  • 4
    This is more readable than the top answer; however, it will iterate the array twice (once for the `map` call and once for the `Object.fromEntries` call). – knguyen Aug 07 '20 at 00:53
  • @knguyen yes true I guess. But in most cases I guess the extra loop won't matter – sktguha Sep 21 '20 at 07:32
  • @knguyen why does it matter if it iterates twice? Are we even sure it will result in a performance hit? – Ben Jones Dec 30 '22 at 22:56
63

Using 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
  • 1,745
  • 1
  • 14
  • 25
46

With lodash, this can be done using 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
  • 9,924
  • 6
  • 74
  • 89
31

Using the spread operator:

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

Demonstration of the code snippet on jsFiddle.

Pedro Lopes
  • 2,833
  • 1
  • 30
  • 36
  • 8
    I'm exactly here because of this! how spread operator performs agains the regular old way of just assign the new key and return the accumulator? since it's creating a new copy each time, then spread will *perform poorly*! – AMTourky Jul 24 '18 at 06:30
  • 3
    now you spread in every iteration. It should be safe to mutate in reducer. ``` const result = arr.reduce( (accumulator, target) => { accumulator[target.key]: target.val; return accumulator }, {}); ``` – MTJ Jan 31 '20 at 12:58
26

You can use Array.prototype.reduce() and actual JavaScript Map instead just a JavaScript Object.

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

What is different between Map And Object?
Previously, before Map was implemented in JavaScript, Object has been used as a Map because of their similar structure.
Depending on your use case, if u need to need to have ordered keys, need to access the size of the map or have frequent addition and removal from the map, a Map is preferable.

Quote from MDN document:
Objects are similar to Maps in that both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. Because of this (and because there were no built-in alternatives), Objects have been used as Maps historically; however, there are important differences that make using a Map preferable in certain cases:

  • The keys of an Object are Strings and Symbols, whereas they can be any value for a Map, including functions, objects, and any primitive.
  • The keys in Map are ordered while keys added to object are not. Thus, when iterating over it, a Map object returns keys in order of insertion.
  • You can get the size of a Map easily with the size property, while the number of properties in an Object must be determined manually.
  • A Map is an iterable and can thus be directly iterated, whereas iterating over an Object requires obtaining its keys in some fashion and iterating over them.
  • An Object has a prototype, so there are default keys in the map that could collide with your keys if you're not careful. As of ES5 this can be bypassed by using map = Object.create(null), but this is seldom done.
  • A Map may perform better in scenarios involving frequent addition and removal of key pairs.
Jun
  • 2,942
  • 5
  • 28
  • 50
  • 1
    You are missing an arrow. Change `(mapAccumulator, obj) {...}` for `(mapAccumulator, obj) => {...}` – mayid Feb 11 '19 at 00:54
25

Tersiest

list.reduce((obj, item) => ({...obj, [item.name]: item.value}), {})

const list = [
  { name: 'abc', value: 123 },
  { name: 'xyz', value: 789 },
  { name: 'she', value: 'her' },
  { name: 'he', value: 'him'}
]
  
console.log(
  list.reduce((obj, item) => ({...obj, [item.name]: item.value}), {})
)
King Friday
  • 25,132
  • 12
  • 90
  • 84
20

es2015 version:

const myMap = new Map(objArray.map(obj => [ obj.key, obj.val ]));
baryo
  • 1,441
  • 13
  • 18
10

There are better ways to do this as explained by other posters. But if I want to stick to pure JS and ol' fashioned way then here it is:

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);
Hinek
  • 9,519
  • 12
  • 52
  • 74
BrownRecluse
  • 1,638
  • 1
  • 16
  • 19
  • is it advised to use reduce method than this method. I feel like using this method. its simple and easy to see everything. – Santhosh Nov 24 '18 at 12:47
  • I love this approach. I think sometimes the simplest code is the best. People are turned off by mutability nowadays, but as long as it's contained, mutability is actually pretty awesome and performant. – Luis Aceituno May 02 '19 at 13:47
  • I am using this because IE11 do not permit the one with Lambda – pvma Jul 28 '20 at 08:18
  • Thank you for posting the only example with multiple keys that are the same, which is the situation I needed to handle. I also like this because as Anthosh said you can actually see what is going on. My only suggestion for improving this answer would be to show the output – SendETHToThisAddress Mar 17 '21 at 21:14
7

If you want to convert to the new ES6 Map do this:

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

Why should you use this type of Map? Well that is up to you. Take a look at this.

Tiago Bértolo
  • 3,874
  • 3
  • 35
  • 53
5

This is what I'm doing in TypeScript I have a little utils library where I put things like this

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

or if you have a identifier other than 'id'

const hash = arrayToHash([{key:1,data:'data'},{key:2,data:'data'}], 'key')
Peter
  • 7,792
  • 9
  • 63
  • 94
  • 1
    If you want to use an object as a key, you'll have to use Map instead of Object because typescript won't let you use objects as keys – Dany Dhondt Feb 12 '19 at 07:20
  • 1
    ```const normalize = (a,f) => a.reduce((m,o)=>(m[o[f]]=o,m),{});``` – loop May 02 '21 at 19:12
4

With lodash:

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

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

// OR: if you want to index the whole item by key:
// const map = _.fromPairs(items.map(item => [item.key, item]));

The lodash fromPairs function reminds me about zip function in Python

Link to lodash

Tho
  • 23,158
  • 6
  • 60
  • 47
3

A small improvement on the reduce usage:

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
  • 2,689
  • 1
  • 24
  • 36
3

the reduce version seems not work. i go with following.

   let map = {};
    items.forEach(v=>{
      map [v.xxx] = v;
    });
RyanShao
  • 429
  • 2
  • 9
2

Using simple Javascript

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
prompteus
  • 1,051
  • 11
  • 26
Partha Roy
  • 1,575
  • 15
  • 16
  • 3
    isn't using .map(...) pointless in this example as you don't return anything in it? I would suggest forEach in this case. – prompteus Aug 27 '17 at 19:40
2

For me, I prefer not to use any map or reduce and just stick with simple for loop.

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

const hash = {};

for (const item of array) {
    hash[item.key] = item;
}

console.log(hash);
Yada
  • 30,349
  • 24
  • 103
  • 144
  • I agree with @Yada. Easy to read and straight forward code is much preferable for sanity. – King Friday Dec 22 '21 at 14:17
  • im a new react MERN developer started 2 years ago and im getting drowned in all this reduce, filter, map stuff..... im learning it though, filter is most confusing to me. – KingJoeffrey Feb 16 '22 at 03:44
2

I would make it more clear in TypeScript in case someone is interested.

interface Person {
  id: number;
  name: string;
}
type Result = Map<number, string>

const input: Array<Person> = [
  {
    id: 123,
    name: "naveen"
  },
  {
    id: 345,
    name: "kumar"
  },
];
const convertedToMap: Result = input.reduce(
  (map: Result, person: Person) => {
    map.set(person.id, person.name);
    return map;
  },
  new Map()
);
Nguyễn Anh Tuấn
  • 1,023
  • 1
  • 12
  • 19
1

try

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

let arr=[
  {id:123, name:'naveen'}, 
  {id:345, name:"kumar"}
];

let fkey = o => o.id; // function changing object to string (key)

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

console.log( toHashMap(arr,fkey) );

// Adding to prototype is NOT recommented:
//
// Array.prototype.toHashMap = function(f) { return toHashMap(this,f) };
// console.log( arr.toHashMap(fkey) );
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
1
/**
 * convert neste object array to Map by object field
 * @param {Array<Object>} array
 * @param {String} fieldName
 * @returns {Map}
*/
function convertNestObjectArrayToMapByField(array, fieldName){
    return new Map(array.map(obj => [obj[fieldName], obj]));
}

/* Example
  const array = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
  ];
  let map = convertNestObjectArrayToMapByField(array, 'key');
  console.log(map);
  console.log(map.get('foo'));
  # Console
  Map(2) {
    'foo' => { key: 'foo', val: 'bar' },
    'hello' => { key: 'hello', val: 'world' }
  }
  { key: 'foo', val: 'bar' }
*/
nextzeus
  • 1,058
  • 8
  • 7
-1

Map the array like this:

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

let [k,v] = items.map(item => [item.key, item.value])   
console.log([k,v]) 

//Output: [ [ 'foo', 'bar' ], [ 'hello', 'world' ] ]
Jacman
  • 1,486
  • 3
  • 20
  • 32
-2

Following is small snippet i've created in javascript to convert array of objects to hash map, indexed by attribute value of object. You can provide a function to evaluate the key of hash map dynamically (run time).

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

You can find the gist here : https://gist.github.com/naveen-ithappu/c7cd5026f6002131c1fa

Naveen I
  • 5,695
  • 2
  • 15
  • 8