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:
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:
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:
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:
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 handle nil slices 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:
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!
Top comments (0)