When working with Go slices, you might assume that a nil slice and an empty slice are completely interchangeable. After all, both have zero length, zero capacity, and support operations like append()
. However, there’s one important case where they behave differently: JSON marshaling.
The Difference
Let’s look at a simple example:
package main
import (
"encoding/json"
"fmt"
)
type Example struct {
NilSlice []int
EmptySlice []int
}
func main() {
e := Example{
EmptySlice: make([]int, 0),
// NilSlice left as nil
}
data, _ := json.Marshal(e)
fmt.Println(string(data))
}
This outputs:
{"NilSlice":null,"EmptySlice":[]}
Why This Matters
This distinction becomes important when:
- You’re building APIs where clients expect consistent JSON structure
- You’re integrating with systems that handle
null
and[]
differently - You need to maintain backward compatibility with existing JSON parsers
Solutions
You have several ways to handle this:
1. Initialize with make()
vv := make([]int, 0)
2. Explicit nil check
if vv == nil {
vv = []int{}
}
3. Use omitempty tag
type MyStruct struct {
Versions []int `json:"versions,omitempty"`
}
Best Practices
Choose your approach based on your requirements:
- Use
omitempty
when the field’s presence is optional - Initialize with
make()
when you always want[]
in your JSON - Use explicit nil checks when you need to convert nil slices to empty ones at specific points
Remember: while nil and empty slices behave the same in most Go operations, JSON marshaling is one of the few cases where the difference becomes visible and potentially significant.