結論:omitemptyは便利だが罠がある
omitemptyタグは空値を省略できて便利だが、falseや0が保存できなくなる罠がある。
特にboolean型で**「false」と「未設定」を区別したい場合は要注意**だ。
omitemptyとは
空値の場合にフィールドを省略するタグ。JSON/Firestoreへの保存時にデータサイズを削減できる。
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"` // 空なら省略
}⚠️ 最大の罠:「空」とみなされる値
以下の値は「空」として扱われ、omitempty付きフィールドから消える:
| 型 | 空とみなされる値 | 保存できない値の例 |
|---|---|---|
| 真偽値 | false | ❌ falseが保存不可 |
| 数値 | 0 | ❌ 0が保存不可 |
| 文字列 | "" | ❌ 空文字列が保存不可 |
| スライス/マップ | nil または長さ0 | ❌ 空配列が保存不可 |
| ポインタ | nil | - |
time.Timeのゼロ値は、JSONでは「空」に当たらず消えない。下の「struct と time.Time は omitempty で消えない」を参照。
実例:boolean型の罠
問題のあるコード
type Config struct {
EnableFeature bool `firestore:"enableFeature,omitempty"`
}
config := Config{EnableFeature: false}
// ❌ Firestoreに保存されない!
// "enableFeature"フィールド自体が消えるこれでは「false」と「未設定」が区別できない。
解決策:ポインタ型を使う
type Config struct {
EnableFeature *bool `firestore:"enableFeature,omitempty"`
}
// パターン1: 未設定
config := Config{EnableFeature: nil}
// → フィールド省略(未設定として扱える)
// パターン2: 明示的にfalse
falseValue := false
config := Config{EnableFeature: &falseValue}
// → ✅ falseとして保存される
// パターン3: true
trueValue := true
config := Config{EnableFeature: &trueValue}
// → ✅ trueとして保存される動作比較
omitemptyあり
type User struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Active bool `json:"active,omitempty"`
}
user := User{Name: "Alice", Age: 0, Active: false}
// JSON: {"name":"Alice"}
// ❌ Age=0 と Active=false は消えるomitemptyなし
user := User{Name: "Alice", Age: 0, Active: false}
// JSON: {"name":"Alice","age":0,"active":false}
// ✅ 0 と false もちゃんと保存されるstruct と time.Time は omitempty で消えない
encoding/json の omitempty が「空」とみなすのは false / 0 / nil ポインタ / nil インタフェース / 長さ 0 の配列・スライス・map・文字列だけ。
判定は型の種類ごとの固定ルールで、ゼロ値の struct はどれにも当たらないので出力される。time.Time{} は西暦 1 年として出る。
Go 1.24 で追加された omitzero は、型のゼロ値 (IsZero() bool を持つ型ならその結果) で省略を決める。
type Range struct{ Min, Max int }
type Event struct {
At time.Time `json:"at,omitempty"`
AtZ time.Time `json:"atZ,omitzero"`
AtP *time.Time `json:"atP,omitempty"`
R Range `json:"r,omitempty"`
RZ Range `json:"rZ,omitzero"`
Items []int `json:"items,omitzero"`
}
b, _ := json.Marshal(Event{Items: []int{}})
fmt.Println(string(b))
// {"at":"0001-01-01T00:00:00Z","r":{"Min":0,"Max":0},"items":[]}| フィールド | ゼロ値のときの出力 |
|---|---|
time.Time + omitempty | "0001-01-01T00:00:00Z" が出る |
time.Time + omitzero | 消える |
*time.Time + omitempty (nil) | 消える |
struct + omitempty | 全フィールドがゼロ値のまま出る |
struct + omitzero | 消える |
- 任意項目の日時は
omitzeroにするか、*time.Timeにして nil で表す omitzeroは空スライス[]int{}を消さない。ゼロ値は nil スライスだけで、長さ 0 は見ない- 効くかはビルドに使う Go の版で決まる。go.mod の
go行が 1.22 でも、Go 1.26.4 でビルドすれば効いた
null とキー欠落の区別など、encoding/json が黙って潰すものは Go の JSON が黙って潰す null と欠落
にまとめた。
Firestore/JSONでの使い分け
データ削減を優先する場合
Email string `json:"email,omitempty"`
// 未入力のメールアドレスは保存しなくていい値の区別が必要な場合
// boolean型: falseと未設定を区別したい
IsAdmin *bool `firestore:"isAdmin,omitempty"`
// 数値型: 0と未設定を区別したい
Score *int `firestore:"score,omitempty"`まとめ
omitemptyは空値を省略してデータサイズを削減できるfalse、0、空文字列は「空」扱いで消える(最大の罠)- 値の区別が必要ならポインタ型を使う
- JSONでは struct と
time.Timeはomitemptyで消えない。消すならomitzero(Go 1.24+) - Firestore の
omitemptyは JSON と同じ判定にtime.Timeのゼロ値を足したもの。ゼロ時刻は Firestore では消え、struct はどちらでも消えない ( Go の Firestore SDK で消える項目と通らない値 )
Some illustrations on this site, including the Go Gopher, are by Renée French and licensed under CC BY 4.0
