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.
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.
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"}
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.
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.
result
is not a hash as requested by the OP.
var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
new Map<string, string>
.
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}
map
call and once for the Object.fromEntries
call).
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}
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value}))));
had to do this change to work with typescript.
Using the spread operator:
const result = arr.reduce(
(accumulator, target) => ({ ...accumulator, [target.key]: target.val }),
{});
Demonstration of the code snippet on jsFiddle.
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.
(mapAccumulator, obj) {...}
for (mapAccumulator, obj) => {...}
es2015 version:
const myMap = new Map(objArray.map(obj => [ obj.key, obj.val ]));
🏆 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}), {}) )
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);
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')
const normalize = (a,f) => a.reduce((m,o)=>(m[o[f]]=o,m),{});
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' }
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
the reduce version seems not work. i go with following.
let map = {};
items.forEach(v=>{
map [v.xxx] = v;
});
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
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()
);
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) );
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);
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' ] ]
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
Array.prototype
!
Success story sharing
result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});
For ES6 one-liner fans :Dresult = new Map(arr.map(obj => [obj.key, obj.val]));
. Most importantly, it makes it super clear that a map is being returned.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.{ "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 bemap[obj.key] = obj;