Go 提案: JSON v2 来了
探索encoding/json/v2中的空值处理改进,提升Go语言的灵活性与兼容性
背景
JSON是一种轻量级的数据交换格式,Go 语言的 encoding/json 包自诞生以来已有十年之久,它凭借其灵活的特性赢得了开发者的青睐。开发者可以通过结构体标签自定义字段的 JSON 表现形式,也可以让 Go 类型自定义其在 JSON 中的表现形式。然而,随着 Go 语言和 JSON 标准的发展,一些功能上的缺失和性能上的限制逐渐暴露出来。
- 功能缺失:比如,不能指定
time.Time类型的自定义格式化,不能在序列化时省略特定的 Go 值等。 - API 缺陷:比如,没有简单的方法来正确地从
io.Reader中反序列化 JSON。 - 性能限制:标准
json包的性能表现并不尽如人意,特别是在处理大量数据时。 - 行为缺陷:比如,对 JSON 语法的错误处理不够严格,大小写不敏感的反序列化等。
就像math/v2一样的,Go 官方提出encoding/json/v2 来解决上面的那些问题,本文主要目标是分析enable/json中 关于空值的一些问题。以及它在 encoding/json/v2中是如何被解决的。本文不涉及 encoding/json/v2 中其他的修改
This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on Medium. Thank you very much.
Omitempty
在encoding/json包中,有这样关于omitempty的描述:
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.
但是,这种预定义的”空”值判断逻辑并不能满足所有实际场景的需求。我们来看一个例子:
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不能处理空struct,比如Post.Categoryomitempty处理time.Time的方式不是 我们理解的UTC =0,也就是1970-01-01 00:00:00,而是0001-01-01T00:00:00Z。
Omitzero标签
在 encoding/json/v2 中,会添加一个新的标签 omitzero,添加了两个功能,来解决上面的两个问题。(目前这个功能还在开发中,不过可以通过go-json-experiment/json提前体验新功能)
- 更好的
time.Time处理。 - 支持自定义
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编码中”空”值处理的痛点上迈出了重要一步。这个方案不仅满足了开发者对更灵活的”空”值定义的需求,还保持了与现有系统的兼容性。目前该omitzero的落地时间尚未确定,最早也要等到Go 1.24版本。此外,encoding/xml等也会效仿json包,增加omitzero标签。
encoding/json/v2 还包括 性能等其他方面的更新, 有兴趣的Gopher 可以 提前了解这些改动,本博客也会一直关注这个proposal
Go语言开始走向成熟。