Featured image of post Go 提案: JSON v2 来了

Go 提案: JSON v2 来了

探索encoding/json/v2中的空值处理改进,提升Go语言的灵活性与兼容性

 

背景

JSON是一种轻量级的数据交换格式,Go 语言的 encoding/json 包自诞生以来已有十年之久,它凭借其灵活的特性赢得了开发者的青睐。开发者可以通过结构体标签自定义字段的 JSON 表现形式,也可以让 Go 类型自定义其在 JSON 中的表现形式。然而,随着 Go 语言和 JSON 标准的发展,一些功能上的缺失和性能上的限制逐渐暴露出来。

  1. 功能缺失:比如,不能指定 time.Time 类型的自定义格式化,不能在序列化时省略特定的 Go 值等。
  2. API 缺陷:比如,没有简单的方法来正确地从 io.Reader 中反序列化 JSON。
  3. 性能限制:标准json包的性能表现并不尽如人意,特别是在处理大量数据时。
  4. 行为缺陷:比如,对 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.

但是,这种预定义的”空”值判断逻辑并不能满足所有实际场景的需求。我们来看一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  
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))  
}

输出结果为:

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

虽然Post 各个字段都添加了omitempty 。但是结果并不像我们预期一样。

  1. omitempty 不能处理空 struct ,比如 Post.Category
  2. omitempty 处理time.Time 的方式不是 我们理解的UTC =0,也就是1970-01-01 00:00:00 ,而是 0001-01-01T00:00:00Z

omitzero标签

encoding/json/v2 中,会添加一个新的标签 omitzero,添加了两个功能,来解决上面的两个问题。(目前这个功能还在开发中,不过可以通过go-json-experiment/json提前体验新功能)

  1. 更好的time.Time 处理。
  2. 支持自定义IsZero函数。
    比如下面下面的代码:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 解决了上面提到的问题。

1
2
{"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语言开始走向成熟。

Licensed under CC BY-NC-SA 4.0
最后更新于 Sep 15, 2024 10:05 CST
使用 Hugo 构建
主题 StackJimmy 设计