Go で JSON のフィールドを省略する:omitempty と omitzero の使い分け
構造体を JSON に変換するとき、値がないフィールドを出力から省きたいケースは多いです。omitempty を使えばゼロ値のフィールドを省略できますが、その挙動には意外な落とし穴があります。Go 1.24 で追加された omitzero との違いも押さえておきましょう。
omitempty の基本
構造体タグに omitempty を付けると、フィールドがゼロ値のとき JSON 出力から省略されます。
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Age int `json:"age,omitempty"`
IsAdmin bool `json:"is_admin,omitempty"`
}
func main() {
user := User{Name: "Alice"}
data, _ := json.Marshal(user)
fmt.Println(string(data))
// {"name":"Alice"}
}Email(空文字列)、Age(0)、IsAdmin(false)はすべてゼロ値なので出力されません。Name には omitempty が付いていないため、空文字列でも出力に含まれます。
各型のゼロ値は以下のとおりです。
| string | ""(空文字列) |
| int / float | 0 |
| bool | false |
| ポインタ | nil |
| スライス | nil(空スライスは省略されない) |
| マップ | nil(空マップは省略されない) |
omitempty の落とし穴
omitempty が問題になるのは、ゼロ値に意味がある場合です。
type Product struct {
Name string `json:"name"`
Price int `json:"price,omitempty"`
Stock int `json:"stock,omitempty"`
}
func main() {
product := Product{Name: "無料サンプル", Price: 0, Stock: 0}
data, _ := json.Marshal(product)
fmt.Println(string(data))
// {"name":"無料サンプル"}
}Price が 0 円の商品も、Stock が 0 個の商品も、その情報をクライアントに返したいはずです。しかし omitempty は「ゼロ値かどうか」だけで判断するため、意味のあるゼロも未設定のゼロも区別できません。
古典的な対処法はポインタ型を使うことです。
type Product struct {
Name string `json:"name"`
Price *int `json:"price,omitempty"`
Stock *int `json:"stock,omitempty"`
}
func main() {
price := 0
stock := 0
product := Product{Name: "無料サンプル", Price: &price, Stock: &stock}
data, _ := json.Marshal(product)
fmt.Println(string(data))
// {"name":"無料サンプル","price":0,"stock":0}
}ポインタの場合、ゼロ値は nil(値が設定されていない状態)です。0 を指すポインタは nil ではないので省略されません。ただしポインタを多用するとコードが複雑になりがちで、nil チェックの手間も増えます。
omitzero(Go 1.24 以降)
Go 1.24 で導入された omitzero は omitempty の問題点を解消するために設計されました。
型ごとに固定されたゼロ値で判定する。int の 0、bool の false、構造体の空値がすべて省略対象になる。
型が IsZero() メソッドを持っていればそれを呼んで判定する。より意味のある「空かどうか」の判断が可能。
omitzero がもっとも威力を発揮するのは time.Time の扱いです。
type Event struct {
Name string `json:"name"`
StartedAt time.Time `json:"started_at,omitempty"`
EndedAt time.Time `json:"ended_at,omitzero"`
}omitempty だと time.Time のゼロ値判定が曖昧で、空の時刻が "0001-01-01T00:00:00Z" として出力されてしまうことがあります。omitzero なら time.Time の IsZero() メソッドが呼ばれるため、ゼロ時刻は確実に省略されます。
自作の型で omitzero を使う
IsZero() bool メソッドを実装すれば、独自の型でも omitzero の恩恵を受けられます。
type OptionalInt struct {
Value int
Set bool
}
func (o OptionalInt) IsZero() bool {
return !o.Set
}
func (o OptionalInt) MarshalJSON() ([]byte, error) {
return json.Marshal(o.Value)
}type Config struct {
Name string `json:"name"`
MaxRetry OptionalInt `json:"max_retry,omitzero"`
}
func main() {
// Set が false → IsZero() が true → 省略される
c1 := Config{Name: "default"}
data1, _ := json.Marshal(c1)
fmt.Println(string(data1))
// {"name":"default"}
// Value が 0 でも Set が true → 出力される
c2 := Config{Name: "custom", MaxRetry: OptionalInt{Value: 0, Set: true}}
data2, _ := json.Marshal(c2)
fmt.Println(string(data2))
// {"name":"custom","max_retry":0}
}ポインタを使わなくても「未設定」と「値が 0」を区別できるようになります。
実務での使い分け
| 文字列・スライスの空チェック | omitempty で十分 |
| int や bool のゼロに意味がある | omitzero またはポインタ |
| time.Time の省略 | omitzero が最適 |
| カスタム型の省略制御 | omitzero + IsZero() を実装 |
| Go 1.23 以前のプロジェクト | omitempty + ポインタで対応 |
既存コードを一斉に書き換える必要はありません。新しく書くコードで数値や時刻を扱う場面では omitzero を選び、文字列やスライスの単純な空チェックには omitempty を使い続ける。この組み合わせが現実的でしょう。











