Published on

TypeScript: Using Maps, a complete guide

Table of Contents

In TypeScript, the Map object is a collection of key-value pairs that allows you to store and retrieve data based on a key. It is similar to the JavaScript object, but with some added functionality and better type safety.

Let's start by creating a Map object:

ts-map.tsx
const myMap = new Map()

This creates an empty Map object. You can also pass an iterable object to the constructor to initialize the Map:

ts-map.tsx
const myMap = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  ['key3', 'value3']
])

Now that we have a Map, let's look at some methods that we can use.

Setting and Getting Values

To set a value in a Map, use the set method:

ts-map.tsx
myMap.set('key', 'value')

To get a value from a Map, use the get method:

ts-map.tsx
const value = myMap.get('key')

Checking if a Key Exists

To check if a key exists in a Map, use the has method:

ts-map.tsx
if (myMap.has('key')) {
  console.log('Key exists!')
} else {
  console.log('Key does not exist.')
}

Removing a Key-Value Pair

To remove a key-value pair from a Map, use the delete method:

ts-map.tsx
myMap.delete('key')

Emptying the Map

To remove all key-value pairs from a Map, use the clear method:

ts-map.tsx
myMap.clear()

Iterating Over a Map

To iterate over a Map, use the forEach method:

ts-map-for-each.tsx
myMap.forEach((value, key) => {
  console.log(`${key}: ${value}`)
})

You can also use a for...of loop to iterate over a Map:

ts-map-for-of.tsx
for (const [key, value] of myMap) {
  console.log(`${key}: ${value}`)
}

Using Objects as Values

The usage of a Map with number/string as keys and Objects as values is commonly used.

ts-map-for-of.tsx
interface Member {
  fullName: string
  subscriptionID?: number
}

const member1: Member = { fullName: 'John Smith', subscriptionID: 123 }
const member2: Member = { fullName: 'Mike Doe' }

const members = new Map<number, Member>()
members.set(1, member1)
members.set(2, member2)

console.log(members.get(1)) // { fullName: 'John Smith', subscriptionID: 123 }
console.log(members.get(2)) // { fullName: 'Mike Doe' }

Using Objects as Keys

In JavaScript, objects can be used as keys in a Map. However, this can lead to unexpected behavior due to object reference equality.

In TypeScript, you can use a custom type as a key by defining an interface and using it as the type parameter for the Map:

ts-map-objects.tsx
interface Member {
  fullName: string
  subscriptionID?: number
}

const member1: Member = { fullName: 'John Smith', subscriptionID: 123 }
const member2: Member = { fullName: 'Mike Doe' }
const member3: Member = { fullName: 'John Smith', subscriptionID: 123 }

const members = new Map<Member, string>()
members.set(member1, 'Premium')
members.set(member2, 'Basic')

console.log(members.get(member1)) // Premium
console.log(members.get(member2)) // Basic

console.log(members.has(member1)) // ✅ true
console.log(members.has(member2)) // ✅ true
console.log(members.has(member3)) // ❌ false
console.log(members.has({ fullName: 'Mike Doe' })) // ❌ false

In this example, we're using a Member interface as the key type for the Map. We can then set values for each member object and retrieve them using the same object reference.

The get() method of the Map object uses strict equality (===) to compare the key passed in to each key in the map.

Caveat

When using objects as keys, the has method only works with the exact same object that was used as a key. Even if an object has the same properties as another object, it will not be considered a key in the Map unless it is the same object instance.

Utils: javalike putIfAbsent

To check if a similar member already exists in the members Map before inserting it, you can modify the set method to first check if a key with the same properties already exists in the Map, and only insert the new key-value pair if it doesn't.

ts-map-putIfAbsent.tsx
interface Member {
  fullName: string
  subscriptionID?: number
}

const member1: Member = { fullName: 'John Doe', subscriptionID: 123 }
const member2: Member = { fullName: 'Jane Smith' }

const members = new Map<Member, string>()
members.set(member1, 'Premium')

const putIfAbsent = (map: Map<Member, string>, newMember: Member, value: string) => {
  const isUniqueKey = Array.from(map.keys()).find(existingMember => {
    return isEqual(existingMember, newMember) //or use lodash's isEqual  
  })
  
  if (!isUniqueKey) {
    map.set(newMember, value)
  }
}

const isEqual = (member1: Member, member2: Member): boolean => {
  return member1.fullName === member2.fullName && member1.subscriptionID === member2.subscriptionID
}

putIfAbsent(members, member1, 'Premium') // doesn't add a new key-value pair
putIfAbsent(members, member2, 'Basic')   // adds a new key-value pair

console.log(members.size) // 2
console.log(members.get(member1)) // 'Premium'
console.log(members.get(member2)) // 'Basic'

Note that this approach only works if the objects have the same properties in the same order.
If the objects have additional properties or the properties are in a different order, this method will not detect them as similar.
In that case, you may need to write a custom comparison function that checks each property individually.

Using Map in TypeScript can make your code more type-safe and easier to work with.
By understanding the basic methods and concepts of Map, you can take advantage of its powerful features in your projects.

See also: