Converting a JavaScript Map to JSON

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.

Hey look, deleting that value totally didn’t work

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.

  1. Create a new array from the existing map
  2. Serialize the array to JSON
  3. 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.

Look at that, both maps match

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.

Alternative to Eval() and Casting in Databound Control

Some of you might already know this, however I found it interesting and helpful. For the case of this example let’s say you have a Person object defined somewhere in your ASP.NET code.

Public Class Person
	Public Property FirstName As String
	Public Property MiddleName As String
	Public Property LastName As String

	Public ReadOnly Property FullName As String
		Get
			Return String.Join(" ", FirstName, MiddleName, LastName)
		End Get
	End Property
End Class

Say for example you have a collection of Persons that you’ve assigned to a webforms repeater. In your ItemTemplate you would like to access the properties of whatever object you’ve bound to the repeater. .NET 4.5 introduced some new options that allow you to strongly type your ASP.NET data controls.

First let’s go through the way it used to be done…

.aspx.vb File (CodeBehind)

'// declare a list for our repeater to use
Dim persons As List(Of Models.Person)

'// populate the list with persons (returned by this random function, who cares what it does)
persons = _personDAO.GetPersons()

'// assign the list to the repeater and bind 'em up
repPeople.DataSource = persons
repPeople.DataBind()

.aspx File

<asp:Repeater ID="repPeople" runat="server">
	<HeaderTemplate><ul class="people"></HeaderTemplate>
	<ItemTemplate>
		<li class="col_12 peopleWrap">
			FullName: <%# Eval("FullName")%>
		</li>
	</ItemTemplate>
	<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

You can use Eval to evaluate a string and output the related value like is done in the example above. This uses reflection to figure out what the current object is and outputs whatever property that you’ve specified in your string. This is just a string so you don’t get any intellisense and you have to remember (ugghhh – remembering what’s that?) what properties you have access to.

You can also cast the object to the type you know that it should be. This is nice because it avoids expensive reflection and it also gives us intellisense. After casting you can access the properties normally with intellisense help. (Not to mention warnings and errors should a property on the source object ever change). I.E. Full Name: <%# CType(Container.DataItem, Models.Person).FullName%>
Casting is nice and all, but it can get a bit tedious and messy, especially if you have to cast every time you want to access a property.

Using .NET 4.5 — Strongly Typed Repeaters
However, thankfully, .NET 4.5 decided to help us out and allow us to strongly type our repeaters. This gives us the best of both worlds, we get intellisense, avoid expensive reflection, and also can easily access properties without extensive use of CType. All you have to do is set the ItemType attribute on the repeater itself.

<asp:Repeater ID="repPeople" runat="server" ItemType="Models.Person">
	<HeaderTemplate><ul class="people"></HeaderTemplate>
	<ItemTemplate>
		<li class="col_12 peopleWrap">
			FullName: <%# Item.FullName%>
		</li>
	</ItemTemplate>
	<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>