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:

  • omitempty will omit the field if it has a zero-value.
  • Zero-values include: 0 for numbers, "" for strings, false for booleans, nil for pointers, slices, maps, interfaces, and channels
  • omitempty will not omit structs, arrays, time.Time even 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:

Try it in the Go Playground

  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.

Further Reading