- Published on
TypeScript: How to Copy One Map to Another Map
Table of Contents
When working with Map in TypeScript, you may come across a situation where you need to copy the contents of one Map to another Map. This can be useful for creating a new Map with the same key-value pairs as an existing Map, or for making a backup of a Map before making changes to it.
Copy via Constructor (Shallow)
Here's an example of how to copy one Map to another Map using a shallow copy:
interface Member {
fullName: string
subscriptionID?: number
}
const member1: Member = { fullName: 'John Smith', subscriptionID: 123 }
const member2: Member = { fullName: 'Mike Doe' }
const membersMap = new Map<number, Member>()
membersMap.set(1,member1)
membersMap.set(2,member2)
const membersMapCloneShallow = new Map<number, Member>(membersMap)
//All maps have same values
/* {
"fullName": "John Smith",
"subscriptionID": 123
}*/
console.log(membersMap.get(1))
console.log(membersMapCloneShallow.get(1))
//Change original object
membersMap.get(1)!.fullName = "mr jitter";
//Cloned object also afected as is by reference
console.log(membersMap.get(1)) // "fullName": "mr jitter"
console.log(membersMapCloneShallow.get(1)) // "fullName": "mr jitter"
Here, we're creating a Map called membersMap
with Member objects as values. We're adding two key-value pairs to the Map using the set
method. Then, we're creating a new Map called membersMapCloneShallow
by passing membersMap
as an argument to the Map constructor. This creates a shallow copy of membersMap
in membersMapCloneShallow
.
Note that when copying a Map, a shallow copy is created. ☹️
This means that the keys and values in the original Map are copied by reference, rather than by value. If the keys or values are mutable objects, changes made to them in one Map will be reflected in the other Map as well.
Copy via Iterator (Shallow and Deep)
Here's an example of how to copy one Map to another Map using iteration:
interface Member {
fullName: string
subscriptionID?: number
}
const member1: Member = { fullName: 'John Smith', subscriptionID: 123 }
const member2: Member = { fullName: 'Mike Doe' }
const membersMap = new Map<number, Member>()
membersMap.set(1,member1)
membersMap.set(2,member2)
let membersMapCloneShallow = new Map<number, Member>()
let membersMapCloneDeep = new Map<number, Member>()
membersMap.forEach((value, key, map) => {
membersMapCloneShallow.set(key, value)
membersMapCloneDeep.set(key, {...value})
})
//All maps have same values
/* {
"fullName": "John Smith",
"subscriptionID": 123
}*/
console.log(membersMap.get(1))
console.log(membersMapCloneShallow.get(1))
console.log(membersMapCloneDeep.get(1))
//Change original object
membersMap.get(1)!.fullName = "mr jitter";
console.log(membersMap.get(1)) // "fullName": "mr jitter"
console.log(membersMapCloneShallow.get(1)) // "fullName": "mr jitter"
console.log(membersMapCloneDeep.get(1)) // "fullName": "John Smith"
Here, with the spread operator we can achieve the deserialization and a new object is constructed to be referenced, and thus we have the deep copy.
Copy via Custom function (Deep)
In that case you'll need to implement a custom function that copies each key and value recursively.
const deepCopy = <K, V>(original: Map<K, V> | Set<V> | any): Map<K, V> | Set<V> | any => {
if (original instanceof Map) {
const copy = new Map();
original.forEach((val, key) => {
copy.set(deepCopy(key), deepCopy(val));
})
return copy
} else if (original instanceof Set) {
const copy = new Set<V>()
original.forEach(val => {
copy.add(deepCopy(val));
})
return copy
} else if (Array.isArray(original)) {
const copy = original.map(val => deepCopy(val))
return copy
} else if (original && typeof original === 'object') {
const copy: any = {};
Object.entries(original).forEach(([key, val]) => {
copy[key] = deepCopy(val)
});
return copy
} else {
return original
}
}
WARNING
Please notice here the forEach signature of iterating a Map vs an Array
Array: forEach(element, index, array)
Map : forEach(value, key, map)
interface Member {
fullName: string
subscriptionID?: number
address?: Address
contacts?: Map<string, string>
}
interface Address {
street: string
city: string
}
//Maps with nested Objects
const membersMap = new Map<number, Member>()
const johnAddress: Address = { street: 'Rue de l Eventail', city: 'Le Mans' };
const johnContacts = new Map<string, string>()
johnContacts.set('email', 'john.smith@domain.com')
johnContacts.set('phone', '12-1234-567890')
const john: Member = {
fullName: 'John Smith',
subscriptionID: 123 ,
address: johnAddress ,
contacts: johnContacts}
membersMap.set(1,john)
//Show contacts
membersMap.get(1)?.contacts?.forEach((value, key) => {
console.log(`contact ${key}: ${value}`)
})
const membersMapDeepClone = deepCopy(membersMap)
membersMap.get(1)!.address!.city = "Paris"
membersMap.get(1)!.address!.street = "Rue Desaix"
console.log(membersMap.get(1)) // "address": {"street": "Rue Desaix","city": "Paris"}
console.log(membersMapDeepClone.get(1)) // "address": {"street": "Rue de l Eventail","city": "Le Mans"}
Here, we're creating a Map membersMap
with Member
objects as keys and Map
as values. We're adding one Nested Object key-value pair for John to the Map using the set
method.
Then, we're creating a new Map membersMapDeepClone
by passing membersMap
to the deepCopy
arrow function, which creates a deep copy of the original Map.
Copy via Deserialization OneLiner (Deep)
This approach of using JSON.parse(JSON.stringify())
to create a deep copy of a Map is a common technique used to clone objects in JavaScript. The way it works is: first serializes the object into a JSON string and then deserializes it into a new object. This process creates a new object with a new reference, thus making a deep copy of the original object.
However, this approach has some limitations, such as not being able to handle circular references or preserving the original object's prototype. In contrast, deepCopy
that provided previously can handle those cases.
Regarding which approach is preferred, it depends on the specific use case and requirements. If your object does not have any circular references, and you don't need to preserve the original object's prototype, then the JSON.parse(JSON.stringify()) approach can be a simpler and faster solution.
However, if you need a more robust and customizable solution that can handle any object, including circular references, then the deepCopy function is a better option.
In general, it's a good practice to use a library or utility function, such as Lodash's
cloneDeep, for cloning objects, as they have been tested and optimized for performance and reliability.
const membersMapDeepClone = new Map(JSON.parse(JSON.stringify(Array.from(membersMap))))
console.log(membersMapDeepClone.get(john)) // ✅ undefined / References disolved
Summary
we have seen :
- Copy via Constructor (Shallow )
- Copy via Iterator (Shallow and Deep)
- Copy via Custom function (Deep)
- Copy via Deserialization OneLiner (Deep)