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
nulland[]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
omitemptywhen 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.