【Go】omitempty vs omitzero
はじめに
Go 1.24から、encoding/jsonにomitzeroというタグが追加されました。
いろいろな記事を読んでomitemptyとの違いがなんとなくしか理解できていなかったので、簡単な例を通して理解を深めつつ、調べたことを備忘録的に書いていこうと思います。
この記事でわかること
-
omitemptyで起こること -
omitzeroの使い方
omitemptyについて
omitemptyタグについて、公式ドキュメントでは以下のように説明されています。
// The "omitempty" option specifies that the field should be omitted
// from the encoding if the field has an empty value, defined as
// false, 0, a nil pointer, a nil interface value, and any array,
// slice, map, or string of length zero.
これをざっくり訳すと、omitemptyではフィールドの値がゼロ値だった場合、エンコーディング対象から除外する、と書かれています。
Goにおける ゼロ値とは、「false」「0」「空文字」「nil(スライスやポインタなど)」 を指します。
それでは、omitemptyタグを用いた場合、どのような挙動になるのか見ていきましょう。
type User struct {
Name string `json:"name,omitempty"`
NickName string `json:"nick_name,omitempty"`
Age int `json:"age,omitempty"`
Address Address `json:"address,omitempty"`
Hobbies []Hobby `json:"hobbies,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
}
type Hobby struct {
Name string `json:"name,omitempty"`
}
type Address struct {
Prefecture string `json:"prefecture,omitempty"`
}
func main() {
user := User{
Name: "Taro",
Age: 20,
}
jsonData, _ := json.Marshal(user)
fmt.Println("jsonData: ", string(jsonData))
}
今回は、User構造体のフィールドのうち、NameとAgeのみ値が入っております。
それ以外が特に指定していないので、ゼロ値が入ることになります。
実行すると、以下のような出力結果を得ることができます。
# go run main.go
jsonData: {"name":"Taro","age":20,"address":{},"created_at":"0001-01-01T00:00:00Z"}
この結果は、以下の3点にまとめることができます。
-
NickNameとHobbiesは省略されている -
Addressはフィールド名と空構造体が出力されている -
CreatedAtには「0001-01-01T00:00:00Z」が入っている
omitemptyタグでは フィールドが空の構造体は「フィールド名と空構造体」 が、time.Time(これも構造体)は「0001-01-01T00:00:00Z」 が出力されてしまうので、ここはomitzeroを使う必要があります。
omitzeroについて
omitzeroタグについて、公式ドキュメントでは以下のように説明されています。
When marshaling, a struct field with the new omitzero option in the struct field tag will be omitted if its value is zero.
(中略)
In particular, unlike omitempty, omitzero omits zero-valued time.Time values, which is a common source of friction.
これをざっくり訳すと、「omitzeroが指定されている場合、その値がゼロであればフィールドは省略される」ことが書かれています。
これだけだとomitemptyと何が違うのか?となりますが、続けて「omitemptyとは異なり、time.Time型のゼロ値を省略する」と書かれており、これがomitemptyとの違いです。
それでは、omitzeroタグをを用いた場合、どのような挙動になるのか見ていきましょう。
type User struct {
Name string `json:"name,omitzero"`
NickName string `json:"nick_name,omitzero"`
Age int `json:"age,omitzero"`
Address Address `json:"address,omitzero"`
Hobbies []Hobby `json:"hobbies,omitzero"`
CreatedAt time.Time `json:"created_at,omitzero"`
}
type Hobby struct {
Name string `json:"name,omitzero"`
}
type Address struct {
Prefecture string `json:"prefecture,omitzero"`
}
func main() {
user := User{
Name: "Taro",
Age: 20,
}
jsonData, _ := json.Marshal(user)
fmt.Println("jsonData: ", string(jsonData))
}
これは先ほどの実装のうち、omitemptyをomitzeroに変更しただけです。
これを実行すると、以下のような出力結果を得ることができます。
# go run main.go
jsonData: {"name":"Taro","age":20}
先ほどは出力されていたAddressとCreatedAtも、omitzeroタグをつけたことで省略されました。
まとめ
omitemptyとomitzeroはどちらも「値が空の場合にJSON出力から省略したい」ときに使うタグですが、適用範囲と挙動に明確な違いがあります。
omitempty
- フィールドがゼロ値の場合に省略する
- ただし構造体はゼロ値でも省略されない
- 空構造体 {} として出力される
-
time.Timeのゼロ値(0001-01-01T00:00:00Z)もそのまま出力される
つまり、omitemptyは「プリミティブ型やスライスに対しては便利だが、構造体やtime.Timeでは使いづらい」場合があるタグと言えます。
omitzero
- フィールドの値がゼロ値なら必ず省略する
- 特に
omitemptyが苦手としていた構造体やtime.Timeのゼロ値も省略 - 「値が入っていないフィールドはJSONから完全に消したい」場面で有効
Go 1.24 以降の新しいタグで、実運用上のニーズに合わせて改善された仕様といえます。
Discussion