If you’ve worked with Go for sometime, you have probably encountered the omitempty
tag, when trying to encode a struct to JSON.
This post will explain what exactly the omitempty
tag means, and some common pitfalls, along with ways to get around them.
Basic Usage
When using Go to build web applications, or HTTP servers, there will eventually come a time when we need to encode some data as JSON. The problem here is that the JSON representation does not exactly match the way Go handles data.
One key difference, is that in a struct
, there are always clearly defined fields, while in JSON there are not. For example, consider describing a dog as a struct in Go:
type Dog struct {
Breed string
WeightKg int
}
Now, if we were to initialize, encode to JSON, and print a sample dog :
func main() {
d := Dog{
Breed: "dalmation",
WeightKg: 45,
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
We would get the output : {"Breed":"dalmation","WeightKg":45}
(Try it here)
Now, a dog for whom we do not have the weight, would be slightly awkward to deal with:
func main() {
d := Dog{
Breed: "pug",
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
This prints {"Breed":"pug","WeightKg":0}
, even though the weight of of pug was unknown. A better option would be to print "WeightKg":null
, or to not have the WeightKg
key at all.
This is where the omitempty
tag helps us. Let’s add the omitempty tag to our Dog
struct:
type Dog struct {
Breed string
// The first comma below is to separate the name tag from the omitempty tag
WeightKg int `json:",omitempty"`
}
Now, if WeightKg
is set to it’s default value (which is 0
for the int
type), the key itself will be omitted from the JSON object.
The same will happen if a string is empty ""
, or if a pointer is nil
, or if a slice has zero elements in it.
Values that Cannot Be Omitted
In cases where an empty value does not exist, omitempty
is of no use. An embedded struct, for example, does not have an empty value:
type dimension struct {
Height int
Width int
}
type Dog struct {
Breed string
WeightKg int
Size dimension `json:",omitempty"`
}
func main() {
d := Dog{
Breed: "pug",
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
This gives the output:
{"Breed":"pug","WeightKg":0,"Size":{"Height":0,"Width":0}}
In this case, even though we never set the value of the Size
attribute, and set its omitempty
tag, it still appears in the output.
This is because structs do not have any empty value in Go. To solve this, use a struct pointer instead :
type Dog struct {
Breed string
WeightKg int
// Now `Size` is a pointer to a `dimension` instance
Size *dimension `json:",omitempty"`
}
Running the same code will now give us:
{"Breed":"pug","WeightKg":0}
Although structs do not have an “empty” value, struct pointers do, with the empty value being nil
.
type Dog struct {
Age *int `json:",omitempty"`
}
func main() {
age := 0
d := Dog{
Age: &age,
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
The important thing to note here, is that Age
is a pointer to an integer.
While an integer has a zero value of 0
, a pointer has a zero value of nil
, and the actual integer 0
is not considered “empty”.
The Difference Between 0, "" and Nil
One issue which particularly caused me a lot a trouble is the case where you want to differentiate between a default value, and a zero value.
For example, if we have a struct describing a restaurant, with the number of seated customers as an attribute:
type Restaurant struct {
NumberOfCustomers int `json:",omitempty"`
}
func main() {
d := Restaurant{
NumberOfCustomers: 0,
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
Running the above code will print:
{}
In this case, the number of customers in a restaurant can actually be 0
, and we would want to convey this information in the JSON object. At the same time, if the NumberOfCustomers
is not set, we would like the key to be omitted.
In other words, we do not want 0
to be the “empty” value for NumberOfCustomers
.
One way to solve this is to use an int
pointer:
type Restaurant struct {
NumberOfCustomers *int `json:",omitempty"`
}
Now the empty value is a nil
pointer, which is different from the value of 0
:
func main() {
d1 := Restaurant{}
b, _ := json.Marshal(d1)
fmt.Println(string(b))
//Prints: {}
n := 0
d2 := Restaurant{
NumberOfCustomers: &n,
}
b, _ = json.Marshal(d2)
fmt.Println(string(b))
//Prints: {"NumberOfCustomers":0}
}
When to use “omitempty”
Although we’ve gone through the usage of omitempty
, the most important thing to remember is that it should be used sparingly. If the application consuming the JSON objects generated by the Go application doesn’t differentiate between undefined keys and zero value keys, then there’s really no reason to use the omitempty
tag in the first place.
If you do decide to use it, then make sure that Go’s definition of “empty” matches your application’s.
To learn how to work with JSON in Go in greater detail, you can read my other post on parsing JSON in Go.