# Go 1.24 omitzero

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

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

# `omitempty`

> `"omitempty"` 指定當字段為「空值」時，序列化時應忽略該字段。
> 
> 「空值」的定義：`false`、`0`、`nil` 指標、`nil` 介面，以及長度為 0 的 array、slice、map、string。

```go
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))
}
```

### 結果

```json
{
  "bar": {
    "val": 0
  }
}
```

* ### 關鍵點：
    
    * `omitempty` 會忽略 `0`、`""`、`nil`、空 slice/map。
        
    * **但 struct (**`Bar`) 無法忽略，即使 `Val=0` 也是如此！
        
    * 空 slice/map (`Arr`、`Map`) 直接被忽略，無法區分 `nil` 與 `empty`。
        
    

# `omitzero`

> `"omitzero"` 指定當字段為「零值」時，應忽略該字段。  
> 「零值」的定義：
> 
> 1. 若該類型有 `IsZero() bool` 方法，則使用該方法的結果。
>     
> 2. 否則，該類型的「零值」就是該類型的預設值 (`0`, `false`, `""`, `nil` 等)。
>     
> 3. 若同時標記 `omitempty` 和 `omitzero`，則符合任何一種條件都會被忽略。
>     

```go
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))
}
```

### 結果

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

## 關鍵點：

* struct (`Bar`) 被忽略了！ 只要它的所有欄位都是「零值」，就會被省略。
    
* slice/map 只會忽略 `nil`，不會忽略 `len=0` 的情況。
    

## `IsZero()`：自訂「零值」邏輯

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

```go
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"`
}
```

```go
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`：比較表

| 類型 | `omitempty` | `omitzero` |
| --- | --- | --- |
| int, float, string, bool, ptr | `0, false, "", nil` | `0, false, "", nil` |
| slice/map | `len=0` (包含 `nil`) | `nil` (不包含 `len=0`) |
| struct | **無法忽略** | 有 `IsZero()` 則判斷 `IsZero() == true` 否則**所有欄位為零值時忽略** |

# 結論

* `omitempty` 適合「只忽略確定為空的標準類型」，但對 struct 無效，且無法區分 `nil` 和 `empty` 的 slice/map。
    
* `omitzero` 更強大，讓 struct 也能被忽略，並且允許 `IsZero()` 來自訂「零值」邏輯。
    
* 若要更細緻地控制 Go 的 JSON 序列化，`omitzero` 是更靈活的選擇。
