DEV Community

John Au-Yeung
John Au-Yeung

Posted on

Storing Key-Value Pairs With JavaScript Maps

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

With ES2015, we have a new data structure to store key-value pairs, called Maps, which are dictionaries that we can use to store any object as keys and any object as values.

Before we had Maps, we had to use objects as dictionaries that only use strings as keys, but values could be anything.

Using Maps is simple, we can define Maps like in the following code:

let messageMap = new Map();  
messageMap.set('hi', 'Hello');  
messageMap.set('greeting', 'How are you doing?');  
messageMap.set('bye', 'Bye');

Instead of using the set method to add our keys and values, we can also pass a nested array where each entry of the array has the key as the first element and the value as the second element.

For example, we can write:

const arrayOfKeysAndValues = [  
  ['a', 1],  
  ['b', 2],  
  ['c', 3],  
  ['d', 3],  
  ['e', 4],  
  ['f', 5],  
  ['g', 6],  
  ['h', 7],  
  ['i', 8],  
  ['j', 9],  
  ['k', 10],  
  ['l', 11],  
  ['m', 12],  
  ['n', 13],  
  ['o', 14],  
]  
let numMap = new Map(arrayOfKeysAndValues);  
console.log(numMap)

The code above will create a Map with the first element of each array as the key and the second element of each array as the corresponding value. So, after running the console.log line, we get all the entries of the Map listed in the same order as the array.

We can get the number of key-value pairs defined in a Map by using the size property:

messageMap.size // 3

To get the value by the key, we use the get function:

messageMap.get('hi'); // 'Hello'  
messageMap.get('hello'); // undefined

It returns the value if the key exists and undefined if an entry with the key doesn’t exist.

To remove an entry given the key, we can use the delete function with the key passed in:

messageMap.delete('hi');

If we call set with the same key passed in more than once, then the value in the later set call overwrites the earlier one. For example:

let messageMap = new Map();  
messageMap.set('hi', 'Hello');  
messageMap.set('greeting', 'How are you doing?');  
messageMap.set('bye', 'Bye');  
messageMap.set('hi', 'Hi');
console.log(messageMap.get('hi'))

If we run console.log(messageMap.get(‘hi’)), we get 'Hi' instead of 'Hello'.

To clear all the entries of a Map object, we can call the clear function, like in the following code:

let messageMap = new Map();  
messageMap.set('hi', 'Hello');  
messageMap.set('greeting', 'How are you doing?');  
messageMap.set('bye', 'Bye');  
messageMap.set('hi', 'Hi');  
messageMap.clear();  
console.log(messageMap)  
console.log(messageMap.size)

We see that messageMap should be empty when we log it and that its size should be 0.

The entries of a Map can be iterated over with a for...of loop. With the destructuring assignment of each entry, we can get the key and value of each entry with a for...of loop like in the following code:

let messageMap = new Map();  
messageMap.set('hi', 'Hello');  
messageMap.set('greeting', 'How are you doing?');  
messageMap.set('bye', 'Bye');

for (let [key, value] of messageMap) {  
  console.log(`${key} - ${value}`);  
}

Objects and Maps are different in multiple ways. The keys of objects can only be of the string type. In Maps, keys can be of any value.

We can get the size of a Map easily with the size property which isn’t available in objects. The iteration order of Maps is in the iteration order of the elements, while the iteration of the keys of an object isn’t guaranteed.

Object has a prototype, so there are default keys in an object, but not in a Map.

Maps are preferred is we want to store non-string keys, when keys are unknown until run time, and when all the keys are the same type and values are the same type which may or may not be the same type as the keys.

We can use the entries function to get the entries of a Map. For example, we can write:

let messageMap = new Map();  
messageMap.set('hi', 'Hello');  
messageMap.set('greeting', 'How are you doing?');  
messageMap.set('bye', 'Bye');

for (let [key, value] of messageMap.entries()) {  
  console.log(`${key} - ${value}`);  
}

To loop through the entries of the Map.

Keys are checked for equality by mostly following the rules of the triple equals operator, except that NaN is considered equal to itself and +0 and -0 are also considered equal. The algorithm is called the same-value-zero algorithm.

So, if we have:

const map = new Map();  
map.set(NaN, 'abc');

Then we run map.get(NaN), we get that 'abc' returned. Also, it’s important to note that we can use non-string values as keys like we did above. For example:

let messageMap = new Map();  
messageMap.set(1, 'Hello');  
messageMap.set(2, 'How are you doing?');  
messageMap.set(3, 'Bye');

If we call messageMap.get(1), we get 'Hello'.

However, since the keys are retrieved by checking with the triple equal operator with NaN considered equal to itself, we cannot get the value that you expect directly from object keys.

let messageMap = new Map();  
messageMap.set({ messageType: 'hi' }, 'Hello');  
messageMap.set({ messageType: 'greeting' }, 'How are you doing?');  
messageMap.set({ messageType: 'bye' }, 'Bye');  
const hi = messageMap.get({  
  messageType: 'hi'  
})
console.log(hi);

hi will be undefined as it doesn’t check the content of the object for equality.

We can use array methods on Map if we convert it to an array first and then convert it back to a Map. For example, if we want to multiply all the values in a Map by 2, then we can write the following:

let numMap = new Map();  
numMap.set('a', 1);  
numMap.set('b', 2);  
numMap.set('c', 3);

let multipliedBy2Array = [...numMap].map(([key, value]) => ([key, value * 2]));

let newMap = new Map(multipliedBy2Array);
console.log(newMap)

As we can see from the code above, we can use the spread operator to convert a Map to an Array directly by copying the values from the Map to the array.

Each entry consists of an array with the key as the first element and the value as the second element. Then we can call map on it since it’s an array. We can then multiply each value’s entry by 2, while keeping each entry’s array structure the same, with key first and value second.

Then we can pass it directly to the constructor of the Map object and we can see that the new Map object, newMap, has all the values multiplied by 2 while keeping the same keys as numMap.

Likewise, we can combine multiple Maps by first putting them in the same array with the spread operator and combining them into one, then passing it to the Map constructor to generate a new Map object.

For example, we can write:

let numMap1 = new Map();  
numMap1.set('a', 1);  
numMap1.set('b', 2);  
numMap1.set('c', 3);  
numMap1.set('d', 3);  
numMap1.set('e', 4);  
numMap1.set('f', 5);  
numMap1.set('g', 6);  
numMap1.set('h', 7);  
numMap1.set('i', 8);  
numMap1.set('j', 9);  
numMap1.set('k', 10);  
numMap1.set('l', 11);let numMap2 = new Map();  
numMap2.set('m', 1);  
numMap2.set('n', 2);  
numMap2.set('o', 3);  
numMap2.set('p', 3);  
numMap2.set('q', 4);  
numMap2.set('r', 5);  
numMap2.set('s', 6);  
numMap2.set('t', 7);  
numMap2.set('u', 8);  
numMap2.set('v', 9);  
numMap2.set('w', 10);  
numMap2.set('x', 11);let numMap3 = new Map();  
numMap3.set('y', 1);  
numMap3.set('z', 2);  
numMap3.set('foo', 3);  
numMap3.set('bar', 3);  
numMap3.set('baz', 4);

const combinedArray = [...numMap1, ...numMap2, ...numMap3];  
const combinedNumMap = new Map(combinedArray);

When we log combinedNumMap, we see that we have all the original keys and values intact, but they were combined into one Map.

If we have overlapping keys, then the one that’s inserted later overwrites the values that were inserted earlier. For example:

let numMap1 = new Map();  
numMap1.set('a', 1);  
numMap1.set('b', 2);  
numMap1.set('c', 3);  
numMap1.set('d', 3);  
numMap1.set('e', 4);  
numMap1.set('f', 5);  
numMap1.set('g', 6);  
numMap1.set('h', 7);  
numMap1.set('i', 8);  
numMap1.set('j', 9);  
numMap1.set('k', 10);  
numMap1.set('l', 11);

let numMap2 = new Map();  
numMap2.set('m', 1);  
numMap2.set('n', 2);  
numMap2.set('o', 3);  
numMap2.set('p', 3);  
numMap2.set('q', 4);  
numMap2.set('r', 5);  
numMap2.set('s', 6);  
numMap2.set('t', 7);  
numMap2.set('u', 8);  
numMap2.set('v', 9);  
numMap2.set('w', 10);  
numMap2.set('x', 11);let numMap3 = new Map();  
numMap3.set('y', 1);  
numMap3.set('z', 2);  
numMap3.set('a', 21);  
numMap3.set('b', 22);  
numMap3.set('c', 23);

const combinedArray = [...numMap1, ...numMap2, ...numMap3];  
const combinedNumMap = new Map(combinedArray);

When we log combinedNumMap, we see that we have most of the original keys and values intact, but they were combined into one Map. However, a now maps to 21, b now maps to 22, and c now maps to 23.

With Maps, we can have key-value pairs where the keys aren’t strings. Maps are collections that can be iterated in the order that the key-value pairs are inserted in.

They can be converted to arrays with each entry being an array with the key as the first element and the value as the second element, which means that we can convert them to arrays and then use arras operations like array methods.

We can use the spread operator to manipulate them in the way that we can do with any arrays, then convert them back to Maps again. We can use the set function to add more entries, and the get function to get the value of the given key.

The values are retrieved by the keys by the same-value-zero algorithm which means the keys are matched by the triple equals operator, with the exception that -0 and +0 are considered equal and NaN is considered equal to itself.

Top comments (2)

Collapse
 
davidyaonz profile image
David Yao

Nice article. Thanks for writing it.

Collapse
 
aumayeung profile image
John Au-Yeung

Thanks very much for reading