February 17, 2021

Removing null values from maps or JSON with Go

Recently I was given a task of removing all keys that have null values from an arbirtrary json. We do data processing of Excel/csv files and have default config generated for them server-side. Users provide custom configuration (column types, names, headers, sorting …) and these two get merged. Problem was - if key existed and had null value it would override the key that had a value. Obviously there may be other ways to do this, but this is a very generic solution that can be used in most cases.

The following gist takes a map of type map[string]interface{} and removes all nil values from it. It can be used for Go built-in maps, JSON, XML or any other serialization format.

It uses reflection to get key type from the map. If the value IsNil, it deletes the key from the map.

Afterwards it checks whether the value is another map[string]interface{} - a json object, and if true uses recursion to go deeper.

It’s definitely not the fastest solution considering it uses reflection and type assertion. For small inputs it’s neglactable, but large ones may take some time.

Before:  map[array:[1 2 3] boolean:true dev:false master:true null:<nil> number:123 object:map[a:b c:d e:<nil>] release:map[123321:<nil> 123331:true picture:http://placehold.it/32x32] staging:<nil> string:Hello World]
After:  map[array:[1 2 3] boolean:true dev:false master:true number:123 object:map[a:b c:d] release:map[123331:true picture:http://placehold.it/32x32] string:Hello World]

Here are benchmark results:

BenchmarkRemoveKeys
BenchmarkRemoveKeys-4   	  102066	     11680 ns/op
func BenchmarkRemoveKeys(b *testing.B) {
	foods := map[string]interface{}{
		"eggs": struct {
			source string
			price  float64
		}{"chicken", 1.75},
		"steak":      true,
		"blue":       nil,
		"_id":        "602cd491455fed74b34da4e1",
		"index":      1,
		"guid":       "f53f16ab-27c7-4439-a466-9214de469271",
		"isActive":   false,
		"balance":    "$1,455.82",
		"picture":    "http://placehold.it/32x32",
		"age":        20,
		"eyeColor":   "brown",
		"name":       "Conway Quinn",
		"gender":     "male",
		"company":    "ISOTRONIC",
		"email":      "conwayquinn@isotronic.com",
		"phone":      "+1 (925) 591-2520",
		"address":    "575 Aster Court, Dundee, Iowa, 7821",
		"about":      "Dolor elit quis exercitation esse dolore Lorem voluptate laborum incididunt. Mollit magna velit voluptate pariatur. Culpa consequat officia magna non do fugiat dolore cupidatat.\r\n",
		"registered": "2015-11-28T03:13:05 -01:00",
		"latitude":   -81.904247,
		"longitude":  135.464116,
		"branches": map[string]interface{}{
			"master":  true,
			"dev":     false,
			"staging": nil,
			"release": map[string]interface{}{
				"123321": nil,
				"123331": true,
				"_id":    "602cd491455fed74b34da4e1",
			},
		},
		"new-branches": map[string]interface{}{
			"master":  true,
			"dev":     false,
			"staging": nil,
			"release": map[string]interface{}{
				"123321": nil,
				"123331": true,
				"index":  1,
			},
		},
		"branches1": map[string]interface{}{
			"master":  true,
			"dev":     false,
			"staging": nil,
			"release": map[string]interface{}{
				"123321": nil,
				"123331": true,
				"guid":   "f53f16ab-27c7-4439-a466-9214de469271",
			},
		},
		"new-branches2": map[string]interface{}{
			"master":  true,
			"dev":     false,
			"staging": nil,
			"release": map[string]interface{}{
				"123321":   nil,
				"123331":   true,
				"isActive": false,
			},
		},
		"branches3": map[string]interface{}{
			"master":  true,
			"dev":     false,
			"staging": nil,
			"release": map[string]interface{}{
				"123321":  nil,
				"123331":  true,
				"balance": "$1,455.82",
			},
		},
		"new-branches4": map[string]interface{}{
			"master":  true,
			"dev":     false,
			"staging": nil,
			"release": map[string]interface{}{
				"123321":  nil,
				"123331":  true,
				"picture": "http://placehold.it/32x32",
			},
			"array": []int{
				1,
				2,
				3,
			},
			"boolean": true,
			"null":    nil,
			"number":  123,
			"object": map[string]interface{}{
				"a": "b",
				"c": "d",
				"e": "f",
			},
			"string": "Hello World",
		},
	}
	for n := 0; n < b.N; n++ {
		RemoveKeys(foods)
	}
}

2018 © Emir Ribic - Some rights reserved; please attribute properly and link back. Code snippets are MIT Licensed

Powered by Hugo & Kiss.