TL;DR Go 1.24 introduced omitzero - a better alternative to omitempty for cleaner JSON output. It handles structs, time.Time, and slices more intuitively. Use it for new projects!
When transforming Go structs to JSON, choosing which field to include is important to make sure that the output is lean, tidy, and makes sense. There are two commonly used structs tags to achieve this: omitempty and omitzero.
For a seasoned Go developer, omitempty is a more familiar term when dealing with omitting fields. However, with the introduction of Go 1.24, omitzero has been added to the language as an alternative to control your JSON output.
Note: You’ll need Go 1.24 or later to use omitzero.
It’s important to understand the differences between these two tags to make an informed decision on which one to use in your codebase.
omitempty
Let’s talk about omitempty first. Key features of omitempty are:
omitemptywill omit the field if it has a zero-value.- Zero-values include:
0for numbers,""for strings,falsefor booleans,nilfor pointers, slices, maps, interfaces, and channels omitemptywill not omitstructs,arrays,time.Timeeven if all the fields are zero-value.
Consider trying it in the Go Playground:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
type Address struct {
Street string `json:"street,omitempty"`
City string `json:"city,omitempty"`
State string `json:"state,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
Country string `json:"country,omitempty"`
}
type User struct {
ID string `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
MiddleName string `json:"middle_name,omitempty"`
Nicknames [3]string `json:"nicknames,omitempty"`
Address Address `json:"address,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
func main() {
// Create struct but left out Address field
user := User{
ID: "user_id_1",
FirstName: "John",
LastName: "Doe",
CreatedAt: time.Now(),
}
js, err := json.MarshalIndent(&user, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(js))
}
Here’s the result:
{
"id": "user_id_1",
"first_name": "John",
"last_name": "Doe",
"nicknames": [
"",
"",
""
],
"address": {},
"created_at": "2009-11-10T23:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
As you can see, Address and Nicknames fields were not provided, they still appeared in the JSON output as empty object {} and empty array ["","",""]. This is because omitempty does not omit structs or array containers themselves, even if all their fields are zero-values. Note that Address’s fields are left out (that’s why it’s an empty object {}), but the parent Address field still appears.
If you look a bit more, you’ll also notice that UpdatedAt field is included with its zero-value timestamp, because omitempty does not omit time.Time fields.
omitzero
This is what omitzero solves. Even the name itself is more descriptive - it omits fields that have zero-values including structs, time.Time, and empty slices/arrays.
Here are just the changes we need to make:
type User struct {
MiddleName string `json:"middle_name,omitzero"`
Nicknames [3]string `json:"nicknames,omitzero"`
Address Address `json:"address,omitzero"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitzero"`
}
{
"id": "user_id_1",
"first_name": "John",
"last_name": "Doe",
"created_at": "2009-11-10T23:00:00Z"
}
Look at that clean, grass-fed, free-range JSON output! The Address, Nicknames, and UpdatedAt fields are completely left out since they all had zero-values.
Key Takeaways
For new code, prefer omitzero - it provides more intuitive behavior and cleaner JSON output:
- Omits all zero-value fields consistently, including structs, time.Time, slices, and empty collections
- The name clearly communicates its purpose
- Results in leaner, more meaningful JSON payloads
When to use omitempty:
- Working with legacy codebases where changing behavior might break clients
- You specifically need to preserve empty structs or time.Time zero values in JSON output
- Maintaining backward compatibility with existing APIs
omitzero represents Go’s evolution toward more predictable JSON marshaling behavior. For greenfield projects, it’s the better default choice.