Thursday, October 9, 2014

Go Map Comparisons and JSON Decoding

TL;DR

  • Be wary when using reflect.DeepEqual on maps. If there is a chance for a nil map then considering adding the length check when checking for equivalence if your use case calls for it.
  • Be aware that when unmarshaling into maps null and {} both result in a nil map

Details

Earlier this week while working on a Go application I was using the internal reflect package to check if two maps were equivalent (using the DeepEqual function). While debugging my code I ran across an interesting situation where the maps I thought were equal (based on fmt.Println statements) were not matching according to reflect.DeepEqual. Here's a decent breakdown of the problem as I initially considered it: What was happening was that I was expecting a JSON object of string-string key-value pairs to be the value for the map key but instead I was occasionally being provided a null value. Based on fmt.Println statements I saw that both paramMap and compMap were "map[]" when this happened. But reflect.DeepEqual still wasn't showing paramMap and compMap were equivalent which in this case was due the operations (JSON decoding) before the reflect comparison. Since the default Go JSON decoder decodes null values to nil the paramMap really had a value of nil and not of an empty map map[]. What then happens is reflect.DeepEqual compares a nil value (hidden by the function parameter specifying a map) to an empty map which you would expect to be equal based on the fmt.Println statements alone; however, that is not the case.

This example code illustrates the confusion a nil map and an empty map can cause which outputs As you can see, a nil map is not equivalent to an empty map despite their string values and lengths being the same. Ultimately, what I decided to do was compare the length of two maps under consideration as well as using reflect.DeepEqual to achieve the desired functionality as shown here (and in the last line of the example code above):
(len(map1) == 0 && len(mapNil) == 0) || reflect.DeepEqual(map1, mapNil)
Here is the interactive example.

While on this topic what do you expect an empty JSON object like {} to decode to when attempting to unmarshal it into map[string]string? An empty map or a nil value? If you guessed an empty map (my original assumption) then you would be wrong. This example code illustrates how a JSON object {} results in a nil map which outputs Here is the interactive example.

Lessons learned:
  • Be wary when using reflect.DeepEqual on maps. If there is a chance for a nil map then considering adding the length check when checking for equivalence if your use case calls for it.
  • Be aware that when unmarshaling into maps null and {} both result in a nil map