Golang 关于 encoding/json/v2 包的新提议

文摘   2024-09-30 17:34   美国  

背景介绍

JSON 是一种轻量级数据交换格式,自十年前 Go 的 encoding/json 包问世以来,开发人员一直对其灵活的特性青睐有加。开发人员可以通过 struct 标记自定义 struct 字段的 JSON 表示法,也允许 Go 类型自定义自己的 JSON 表示法。然而,随着 Go 语言和 JSON 标准的发展,一些功能缺陷和性能限制也逐渐暴露出来。

  • 功能缺失:例如,无法为 time.Time 类型指定自定义格式,无法在序列化过程中省略特定的 Go 值等。

  • API 的缺陷:例如,没有简单的方法从 io.Reader.Reader 中正确反序列化 JSON。

  • 性能限制:标准 json 软件包的性能并不令人满意,尤其是在处理大量数据时。

  • 行为缺陷:例如,JSON 语法的错误处理不够严格,反序列化不区分大小写等。

与 math/v2 一样,Go 官方也提出了 encoding/json/v2[1] 来解决上述问题。本文的主要目的是分析在 encoding/json 中有关空值的一些问题,以及在 encoding/json/v2 中如何解决这些问题。本文不涉及 encoding/json/v2 中的其他修改。

omitempty 行为

encoding/json 包中,对 omitempty 有如下描述:

omitemptyoption 指定,如果字段的值为空(定义为 false、0、nil 指针、nil 接口值以及任何空数组、片、映射或字符串),则编码中应省略该字段。

这种预定义的 nil value 的判断逻辑并不能满足所有实际场景的需要。让我们来看一个例子:

type Post struct {  
    Id         int64           `json:"id,omitempty"`  
    CreateTime time.Time       `json:"create_time,omitempty"`  
    TagList    []Tag           `json:"tag_list,omitempty"`  
    Name       string          `json:"name,omitempty"`  
    Score      float64         `json:"score,omitempty"`  
    Category   Category        `json:"category,omitempty"`  
    LikePost   map[string]Post `json:"like,omitempty"`  
}  
type Tag struct {  
    ID   string `json:"id"`  
    Name string `json:"name"`  
}  
type Category struct {  
    ID   float64 `json:"id"`  
    Name string  `json:"name"`  
}  
  
func main() {  
    b, _ := json.Marshal(new(Post))  
    fmt.Println(string(b))  
}

输出结果为:

{"create_time":"0001-01-01T00:00:00Z","category":{"id":0,"name":""}}

虽然在 Post 的每个字段中都添加了 omitempty,但结果并不尽如人意。

  • omitempty 不能处理空结构,如 Post.Category
  • omitempty 处理 time.Time 的方式不是我们理解的 UTC =0,即 1970-01-01 00:00:00,而是 0001-01-01T00:00:00Z。
更多关于 json 异常行为的坑可以阅读:
Golang 中 JSON 操作的 5 个常见陷阱(建议收藏!)
Golang 如何动态解析 JSON
golang 使用 UnmarshalJSON 接口实现自定义 unmarshal 的坑

omitzero Tag

encoding/json/v2[2] 中,将添加一个新标签 omitzero,它增加了两个功能来解决上述两个问题。(此功能仍在开发中,开发者可以通过 go-json-experiment/json 提前体验新功能)

  • 更好地处理时间类型
  • 支持自定义 IsZero 函数,例如以下代码:
package main  
  
import (  
    "encoding/json"  
    "fmt"    v2_json "github.com/go-json-experiment/json"  
    "math"    "time")  
  
type Post struct {  
    Id         int64           `json:"id,omitempty,omitzero"`  
    CreateTime time.Time       `json:"create_time,omitempty,omitzero"`  
    TagList    []Tag           `json:"tag_list,omitempty"`  
    Name       string          `json:"name,omitempty"`  
    Score      ScoreType       `json:"score,omitempty,omitzero"`  
    Category   Category        `json:"category,omitempty,omitzero"`  
    LikePost   map[string]Post `json:"like,omitempty"`  
}  
type ScoreType float64  
  
func (s ScoreType) IsZero() bool {  
    return s < math.MinInt64  
}  
  
type Tag struct {  
    ID   string `json:"id"`  
    Name string `json:"name"`  
}  
type Category struct {  
    ID   float64 `json:"id"`  
    Name string  `json:"name"`  
}  
  
func main() {  
    v1String, _ := json.Marshal(new(Post))  
    fmt.Println(string(v1String))  
    v2String, _ := v2_json.Marshal(new(Post))  
    fmt.Println(string(v2String))  
}

与 encoding/json 相比,encoding/json/v2 解决了上述问题。

输出结果为:

{"create_time":"0001-01-01T00:00:00Z","category":{"id":0,"name":""}}
{"score":0}

结论

通过引入 omitzero 标记,Go 在解决 JSON 编码中 nil value 处理的痛点方面迈出了非常关键的一步。这一解决方案不仅满足了开发人员对更灵活的 nil 值定义的需求,还保持了与现有系统的兼容性。omitzero 的登陆时间尚未确定,最早也要等到 Go 1.24 版本。

此外,encoding/xml 和其他软件包也将遵循 json 软件包,并添加 omitzero 标记。encoding/json/v2 还包括其他方面的更新,例如性能。感兴趣的 Gophers 可以提前了解这些变化,本博客也将继续关注这一提议。

参考资料
[1]

json/v2 disscussion: https://github.com/golang/go/discussions/63397

[2]

json/v2/disscusstion: https://github.com/golang/go/discussions/63397


Go Official Blog
Golang官方博客的资讯翻译及独家解读
 最新文章