Skip to main content

Command Palette

Search for a command to run...

Go 1.24 omitzero

解決 omitempty 的限制

Updated
2 min read

Go 1.24 新增了一個新的 struct tag omitzero,解決了 omitempty 無法忽略 struct 以及 nil vs empty slice/map 不可區分的問題。

在介紹 omitzero 之前,讓我們先回顧 omitempty 的行為。

omitempty

"omitempty" 指定當字段為「空值」時,序列化時應忽略該字段。

「空值」的定義:false0nil 指標、nil 介面,以及長度為 0 的 array、slice、map、string。

type Foo struct {
    Int    int            `json:"int,omitempty"`
    Str    string         `json:"str,omitempty"`
    Arr    []int          `json:"arr,omitempty"`
    Map    map[string]int `json:"map,omitempty"`
    Bar    Bar            `json:"bar,omitempty"`
    BarPtr *Bar           `json:"bar_ptr,omitempty"`
}

type Bar struct {
    Val int `json:"val"`
}

func main() {
    foo := Foo{
        Int:   0,
        Str:   "",
        Arr:   []int{},
        Map:   map[string]int{},
        Bar:   Bar{},           // 這裡是關鍵!
        BarPtr: nil,
    }

    bs, _ := json.MarshalIndent(foo, "", "  ")
    fmt.Println(string(bs))
}

結果

{
  "bar": {
    "val": 0
  }
}
  • 關鍵點:

    • omitempty 會忽略 0""nil、空 slice/map。

    • 但 struct (Bar) 無法忽略,即使 Val=0 也是如此!

    • 空 slice/map (ArrMap) 直接被忽略,無法區分 nilempty

omitzero

"omitzero" 指定當字段為「零值」時,應忽略該字段。
「零值」的定義:

  1. 若該類型有 IsZero() bool 方法,則使用該方法的結果。

  2. 否則,該類型的「零值」就是該類型的預設值 (0, false, "", nil 等)。

  3. 若同時標記 omitemptyomitzero,則符合任何一種條件都會被忽略。

type Foo struct {
    Int    int            `json:"int,omitzero"`
    Str    string         `json:"str,omitzero"`
    Arr    []int          `json:"arr,omitzero"`
    Map    map[string]int `json:"map,omitzero"`
    Bar    Bar            `json:"bar,omitzero"`
    BarPtr *Bar           `json:"bar_ptr,omitzero"`
}

type Bar struct {
    Val int `json:"val"`
}

func main() {
    foo := Foo{
        Int:   0,
        Str:   "",
        Arr:   []int{},
        Map:   map[string]int{},
        Bar:   Bar{},           // 這裡是關鍵!
        BarPtr: nil,
    }

    bs, _ := json.MarshalIndent(foo, "", "  ")
    fmt.Println(string(bs))
}

結果

{
    "arr": [],
    "map": {}
}

關鍵點:

  • struct (Bar) 被忽略了! 只要它的所有欄位都是「零值」,就會被省略。

  • slice/map 只會忽略 nil,不會忽略 len=0 的情況。

IsZero():自訂「零值」邏輯

允許 struct 定義 IsZero() 方法,讓 omitzero 可依據業務需求決定何時省略 struct。

type Bar struct {
    Enable bool `json:"enable"`
    Val    int  `json:"val"`
}

func (b Bar) IsZero() bool {
    return !b.Enable  // 只有 Enable=false 才視為零值
}

type Foo struct {
    Bar Bar `json:"bar,omitzero"`
}
foo1 := Foo{Bar: Bar{Enable: false, Val: 1}}
// 結果:{}

foo2 := Foo{Bar: Bar{Enable: true, Val: 1}}
// 結果:{"bar":{"enable":true,"val":1}}

這讓我們可以更細緻地控制何時省略 struct,例如:

  • Enable=false 時,整個 struct 都被忽略,即使 Val != 0

  • Enable=true 時,則保留整個 struct。

omitempty vs omitzero:比較表

類型omitemptyomitzero
int, float, string, bool, ptr0, false, "", nil0, false, "", nil
slice/maplen=0 (包含 nil)nil (不包含 len=0)
struct無法忽略IsZero() 則判斷 IsZero() == true 否則所有欄位為零值時忽略

結論

  • omitempty 適合「只忽略確定為空的標準類型」,但對 struct 無效,且無法區分 nilempty 的 slice/map。

  • omitzero 更強大,讓 struct 也能被忽略,並且允許 IsZero() 來自訂「零值」邏輯。

  • 若要更細緻地控制 Go 的 JSON 序列化,omitzero 是更靈活的選擇。

More from this blog

簡介 C++ 的 Type Erase (用多型和模板做 Duck Type)

起點 讓我們先從 template 出發:foo 需要一個 callback function。 template<typename Func> void foo(Func callback) { // ... callback(); } 但是這會讓編譯錯誤訊息有點模糊:假如 callback 並不是一個可以呼叫的函數指標,或者並不是一個 callable object ,那編譯器會說錯出在第四行。但是我們都希望,編譯器在呼叫函數時就幫我們指出:這不是 foo 想要的 call...

May 14, 20243 min read

帕秋莉的魔法筆記

45 posts

後端工程師。

不定時張貼一些寫扣時的筆記。