Go 1.24:omitzero简化JSON处理


Go 1.24 的 omitzero 是近年来 Go 语言生态系统中一个非常棒的更新。几周前,Go 1.24 Tools发布了,这是多年来生态系统中最棒的补充之一。之后,一些评论者提醒我,Go 1.24 版本中还有一个重要的新功能 omitzero,我意识到我在发布说明中漏掉了它。

忽略 JSON 中的可选字段 omitempty(Go 1.24 之前)
当你有一个结构体需要转换成 JSON 时,可能会有一些可选的字段。

例如,我们来看以下结构体:


type Response struct {
    ID        string    <code>json:"id"</code>
    
// 其他字段省略
    UpdatedAt time.Time <code>json:
"updated_at"</code>
}

假设 UpdatedAt 只有在数据库中的记录被更新后才应该设置。

考虑到这一点,如果字段有值,我们可能只想在 JSON 响应中返回该字段的值。

如果我们使用上述结构体并编组数据(UpdatedAt 为零值):

func main() {
    resp := Response_noTag{
        ID: "the-id",
        
// 没有显式初始化 UpdatedAt
    }

    data, err := json.MarshalIndent(resp,
"", "  ")
    must(err)

    fmt.Printf(
"%s\n", data)
}

这会导致出现不需要的 updated_at JSON 字段:

j

{
  "id": "the-id",
 
"updated_at": "0001-01-01T00:00:00Z"
}

从 Go 1.23(及之前版本)开始,实现可选 updated_at 的最佳方法是使用指针和 omitempty JSON 结构标签:

type Response struct {
    ID        string     <code>json:"id"</code>
    UpdatedAt *time.Time <code>json:
"updated_at,omitempty"</code>
}

这允许你不指定 UpdatedAt,导致其零值(nil),该零值将通过结构标签 omitempty 被视为缺失,因此不会被编组:


{
  "id": "the-id"
}

这种方法工作得很好,但可能有点不方便,因为你现在必须使用指针。

对于那些认识我的人来说,我是 oapi-codegen 的共同维护者,你会明白,如何在 Go 中最好地处理 JSON 是我非常关心的问题。多年来,我们尝试了各种方法来简化用户体验,但都没有达到目标。

忽略 JSON 中的可选字段 omitzero(从 Go 1.24 开始)
然而,从 Go 1.24 开始,我们现在有了 JSON 结构标签 omitzero,它允许我们使用类型的实际零值作为不被编组的指示。

这使我们能够使用以下结构定义:

type Response_omitzero struct {
    ID        string    <code>json:"id"</code>
    UpdatedAt time.Time <code>json:
"updated_at,omitzero"</code>
}

请注意,我们现在可以使用非指针类型,这改进了我们与类型的交互方式,并且它按预期进行编组:


{
  "id": "the-id"
}

这为什么这么棒呢?
如上所述,一个关键点是减少处理大量额外指针。尽管在这个虚构的例子中只有一个指针,但当使用可选字段嵌套结构时,这可能会变得非常尴尬:

type Response struct {
    Human *Human <code>json:"human"</code>
}

type Human struct {
    DNA  []byte <code>json:
"dna"</code>
    Name string <code>json:
"name"</code>
    Age  *int   <code>json:
"age"</code>
}

这会使一次性初始化结构变得更加困难,因为你无法获取指向常量值的指针来设置 Age: &30。

return Response{
    Human: &Human{
        Age: &30, // 无效操作:无法获取 30 的地址(未类型化的 int 常量)
    },
}

当使用这样的代码时,这可能会有点麻烦,并且会变得相当重复,所以如果你可以避免使用指针繁重的类型,那就太棒了!

正如 omitzero 的提案中所述,这也具有更清晰的语义,因为 struct{} 可以称为“空”,但根据 omitempty 不会被视为“空”。