Ok, let’s cut to the chase. Everyone knows that converting a JavaScript Map to JSON does not work out of the box. By default, the JSON.stringify
method will return a {}
when converting a Map. It’s super helpful, because it returns an empty object, which is usually precisely what we don’t want. That’s a wee bit frustrating!
How Not to Serialize a Map to JSON
The internet has provided us with the silver bullet, though, right? Object.entries
is great and can do all things, right? WRONG. That is wrong and you are wrong if you think that it’s right. You see what I’m getting at?
The existing advice is to use Object.fromEntries
and Object.entries
in tandem to make it easy to convert a map to and from JSON. The code below shows you this method in all it’s glory.
// ### inspired code below
const mapToSerialize = new Map([[1, 'first key']])
const serializedMap = JSON.stringify(Object.fromEntries(mapToSerialize ))
// now deserialize
const deserializedMap = new Map(Object.entries(JSON.parse(serializedMap)))
Isn’t the code above beautiful? Doesn’t it just wonderfully handle our serialization in the best of ways? No! It doesn’t. Let’s compare the original map to the deserialized map and see how amazing the code works.
The original map looks like this when logged to the console Map(1) {1 => 'first key'}
. The deserialized map looks like this when logged to the console: Map(1) {'1' => 'first key'}
. Did you spot the difference?
Just to make things clear, let’s try to delete the keyed value out of our Map.
So really, in the end, our serialization using the currently advised method totally works. We serialize our map, deserialize it, and then can’t use it properly. Just as we expected.
NO. It doesn’t work correctly. The difference is that the original map uses an integer key, and the deserialized map uses a string key. When we attempt to delete the value out of the deserialized map, using the key, it fails because the type doesn’t match.
How to Serialize a Map to JSON
I am proposing a different way to serialize a map to JSON.
- Create a new array from the existing map
- Serialize the array to JSON
- Create a new map from the deserialized JSON
The code I propose looks like this. You might not want to keep the logging at the end in your own implementations.
/// ### ugly code below
const mapToSerialize = new Map([[1, 'first key']])
// convert the map to JSON
const arrayToSerialize = []
mapToSerialize.forEach((value, key) => arrayToSerialize.push([key, value]))
const serializedMap = JSON.stringify(arrayToSerialize)
// convert the JSON back to a map
const deserializedMap = new Map(JSON.parse(serializedMap))
// log to console for inspection
console.log(mapToSerialize)
console.log(deserializedMap)
This code correctly works to serialize and deserialize a map to/from JSON.
In Conclusion
Serializing a Map to and from JSON isn’t as simple as it looks on the outside. Especially if from the outside it looks like it would be super simple. The Object.entries
silver bullet only works for Maps with string keys, and attempting to use it for Maps with integer keys will end up with you hating your life. You probably will start listening to Barry Manilow constantly. You might even start eating Quinoa. I’m sure it happens all of the time due to Map serialization woes.
Anyways, if you prefer to not eat Quinoa, then go ahead and convert your Maps to and from JSON the way I suggested above.
That’s all I have to say about that.
In modern Typescript you can do it like that:
“`typescript
// ### inspired code below
const mapToSerialize = new Map([[1, ‘first key’]])
const serializedMap = JSON.stringify([…Object.fromEntries(mapToSerialize )])
“`
This will keep the types like `number` from the map, and will not convert them to a string.