ChatGPT解决这个技术问题 Extra ChatGPT

Convert object array to hash map, indexed by an attribute value of the Object

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.

You can use JavaScript Map instead of Object. Check out stackoverflow.com/a/54246603/5042169

v
vsync

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.


result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {}); For ES6 one-liner fans :D
@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.
@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.
@Mtz Fair enough.
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;
R
Ryan Shillington

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


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.
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.
P.S. result is not a hash as requested by the OP.
Another typescript version: var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
@AlexV that is a type assertion and should be avoided whenever possible. Instead you can type the Map using new Map<string, string>.
S
Sergey Vyacheslavovich Brunov

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}


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 yes true I guess. But in most cases I guess the extra loop won't matter
s
shuk

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}

Perfect, just what I needed ;)
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value})))); had to do this change to work with typescript.
Important to note that this method does not result with a Map but an object
s
splintor

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}

That is not a hashmap
P
Pedro Lopes

Using the spread operator:

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

Demonstration of the code snippet on jsFiddle.


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!
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 }, {}); ```
J
Jun711

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.


You are missing an arrow. Change (mapAccumulator, obj) {...} for (mapAccumulator, obj) => {...}
b
baryo

es2015 version:

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

K
King Friday

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


H
Hinek

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

is it advised to use reduce method than this method. I feel like using this method. its simple and easy to see everything.
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.
I am using this because IE11 do not permit the one with Lambda
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
T
Tiago Bértolo

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.


P
Peter

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

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
const normalize = (a,f) => a.reduce((m,o)=>(m[o[f]]=o,m),{});
M
Mor Shemesh

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

Is it faster than the other answer?
@orad probably not as it spreads accumulator and creates new object each iteration.
T
Tho

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


R
RyanShao

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

   let map = {};
    items.forEach(v=>{
      map [v.xxx] = v;
    });

m
markcheeky

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

isn't using .map(...) pointless in this example as you don't return anything in it? I would suggest forEach in this case.
N
Nguyễn Anh Tuấn

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

K
Kamil Kiełczewski

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


Y
Yada

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

I agree with @Yada. Easy to read and straight forward code is much preferable for sanity.
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.
J
Jacman

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

N
Naveen I

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


Please, please, please don't recommend extending Array.prototype!
Ahh, I see. I initially thought this was a proposed answer :)