Handling Null JSON Arrays in Go
One of the most frustrating things with Go is how it handles empty arrays when encoding JSON. Rather than returning what is traditionally expected, an empty array, it instead returns null. For example, the following:
package main
import (
"encoding/json"
"fmt"
)
// Bag holds items
type Bag struct {
Items []string
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
response, _ := json.Marshal(payload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
PrintJSON(bag1)
}
Outputs:
{"Items":null}
This occurs because of how the json package handles nil slices:
Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON value.
There are some proposals to amend the json package to handle nil slices:
- encoding/json
nilasempty
to encode nil-slices as[]
- encoding/json: “nonil” struct tag to marshal nil slices and maps as non-null
But, as of this writing, these proposals have not been accepted. As such, in order to overcome the problem of null arrays, we have to set nil slices to empty slices. See the addition of line 22 in the following:
package main
import (
"encoding/json"
"fmt"
)
// Bag holds items
type Bag struct {
Items []string
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
response, _ := json.Marshal(payload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
bag1.Items = make([]string, 0)
PrintJSON(bag1)
}
Changes the output to:
{"Items":[]}
However, this can become quite tedious to do everywhere there can potentially be a nil slice. Is there a better way to do this? Let’s see!
Method 1: Custom Marshaler
According to the Go json docs:
Marshal traverses the value v recursively. If an encountered value implements the Marshaler interface and is not a nil pointer, Marshal calls its MarshalJSON method to produce JSON.
So, if we implement the Marshaler
interface:
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
Our MarshalJSON()
will be called when encoding the data. See the additional MarshalJSON()
at line 14:
package main
import (
"encoding/json"
"fmt"
)
// Bag holds items
type Bag struct {
Items []string
}
// MarshalJSON initializes nil slices and then marshals the bag to JSON
func (b Bag) MarshalJSON() ([]byte, error) {
type Alias Bag
a := struct {
Alias
}{
Alias: (Alias)(b),
}
if a.Items == nil {
a.Items = make([]string, 0)
}
return json.Marshal(a)
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
response, _ := json.Marshal(payload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
PrintJSON(bag1)
}
This would then output:
{"Items":[]}
The Alias
on line 15 is required to prevent an infinite loop when calling json.Marshal()
.
Method 2: Dynamic Initialization
Another way to do this is to use the reflect package to dynamically inspect every field of a struct; if it’s a nil slice, replace it with an empty slice. See NilSliceToEmptySlice()
on line 15:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
// Bag holds items
type Bag struct {
Items []string
}
// NilSliceToEmptySlice recursively sets nil slices to empty slices
func NilSliceToEmptySlice(inter interface{}) interface{} {
// original input that can't be modified
val := reflect.ValueOf(inter)
switch val.Kind() {
case reflect.Slice:
newSlice := reflect.MakeSlice(val.Type(), 0, val.Len())
if !val.IsZero() {
// iterate over each element in slice
for j := 0; j < val.Len(); j++ {
item := val.Index(j)
var newItem reflect.Value
switch item.Kind() {
case reflect.Struct:
// recursively handle nested struct
newItem = reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(item.Interface())))
default:
newItem = item
}
newSlice = reflect.Append(newSlice, newItem)
}
}
return newSlice.Interface()
case reflect.Struct:
// new struct that will be returned
newStruct := reflect.New(reflect.TypeOf(inter))
newVal := newStruct.Elem()
// iterate over input's fields
for i := 0; i < val.NumField(); i++ {
newValField := newVal.Field(i)
valField := val.Field(i)
switch valField.Kind() {
case reflect.Slice:
// recursively handle nested slice
newValField.Set(reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(valField.Interface()))))
case reflect.Struct:
// recursively handle nested struct
newValField.Set(reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(valField.Interface()))))
default:
newValField.Set(valField)
}
}
return newStruct.Interface()
case reflect.Map:
// new map to be returned
newMap := reflect.MakeMap(reflect.TypeOf(inter))
// iterate over every key value pair in input map
iter := val.MapRange()
for iter.Next() {
k := iter.Key()
v := iter.Value()
// recursively handle nested value
newV := reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(v.Interface())))
newMap.SetMapIndex(k, newV)
}
return newMap.Interface()
default:
return inter
}
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
newPayload := NilSliceToEmptySlice(payload)
response, _ := json.Marshal(newPayload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
PrintJSON(bag1)
}
This would then output:
{"Items":[]}
Review
The drawback of the custom marshaler is you have to write one for every struct that has slices. Because it’s custom, though, it can target the specific field that might be a nil slice.
The dynamic initialization approach is definitely slower because every field of the struct needs to be inspected to see if it needs to be replaced. However, this approach works well if you have lots of structs with slices and few places where you call json.Marshal()
.
Which approach would you use? Let me know in the comments below!