QXQZX's Blog

使用

熟悉 Golang 的朋友对于 tag、json 和 struct 都不陌生。

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
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code"`
}

func TestMarshal(t *testing.T) {
data := `{
"city": "Beijing",
"street": "a"
}`
addr := &Address{}
json.Unmarshal([]byte(data), addr)
fmt.Printf("%#v\n", addr)

addressBytes, _ := json.MarshalIndent(addr, "", " ")
fmt.Printf("%s\n", string(addressBytes))
}
/*
&omitempty.Address{City:"Beijing", Street:"a", ZipCode:""}
{
"city": "Beijing",
"street": "a",
"zip_code": ""
}
*/

我们可以看到,多了一行 "zip_code": "", ,而这则信息在原本的 json 数据中是没有的,但我们更希望的是,在一个地址有 zip_code 号码的时候输出,不存在 zip_code 的时候就不输出,幸运的是,我们可以在 Golang 的结构体定义中添加 omitempty 关键字,来表示这条信息如果没有提供,在序列化成 json 的时候就不要包含其默认值。稍作修改,地址结构体就变成了

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
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code,omitempty"`
}

func TestMarshal(t *testing.T) {
data := `{
"city": "Beijing",
"street": "a"
}`
addr := &Address{}
json.Unmarshal([]byte(data), addr)
fmt.Printf("%#v\n", addr)

addressBytes, _ := json.MarshalIndent(addr, "", " ")
fmt.Printf("%s\n", string(addressBytes))
}
/*
&omitempty.Address{City:"Beijing", Street:"a", ZipCode:""}
{
"city": "Beijing",
"street": "a"
}
*/

成功解决。


陷阱

带来方便的同时,使用 omitempty 也有些小陷阱。

陷阱一:关键字无法忽略掉嵌套结构体

还是拿地址类型说事,这回我们想要往地址结构体中加一个新的结构体来表示经纬度,如果没有或缺乏相关的数据,可以忽略。新的 struct 定义如下所示

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
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code,omitempty"`
Coordinate coordinate `json:"coordinate,omitempty"`
}

type coordinate struct {
Lat float64 `json:"latitude"`
Lng float64 `json:"longitude"`
}

func TestMarshal(t *testing.T) {
data := `{
"city": "Beijing",
"street": "a"
}`
addr := &Address{}
json.Unmarshal([]byte(data), addr)

addressBytes, _ := json.MarshalIndent(addr, "", " ")
fmt.Printf("%s\n", string(addressBytes))
}
/*
{
"city": "Beijing",
"street": "a",
"coordinate": {
"latitude": 0,
"longitude": 0
}
}
*/

读入原来的地址数据,处理后序列化输出,我们就会发现即使对新的结构体字段加上了 omitempty 关键字,输出的 json 还是带上了一个空的坐标信息。

为了达到我们想要的效果,可以把坐标结构体定义为指针类型,这样 Golang 就能知道一个指针的“空值”是多少了,否则面对一个我们自定义的结构, Golang 是猜不出我们想要的空值的。于是有了如下的结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code,omitempty"`
Coordinate *coordinate `json:"coordinate,omitempty"`
}

type coordinate struct {
Lat float64 `json:"latitude"`
Lng float64 `json:"longitude"`
}
// ... 同上 忽略
/*
{
"city": "Beijing",
"street": "a"
}
*/

2. 陷阱二:想要传入零值

对于用 omitempty 定义的 字段 ,如果给它赋的值恰好等于默认空值的话,在转为 json 之后也不会输出这个 字段 。比如说上面定义的经纬度坐标结构体,如果我们将经纬度两个 字段 都加上 omitempty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type coordinate struct {
Lat float64 `json:"latitude,omitempty"`
Lng float64 `json:"longitude,omitempty"`
}

func TestMarshal(t *testing.T) {
data := `{
"latitude": 1.0,
"longitude": 0.0
}`
c := &coordinate{}
json.Unmarshal([]byte(data), c)
fmt.Printf("%#v\n", c)

addressBytes, _ := json.MarshalIndent(c, "", " ")
fmt.Printf("%s\n", string(addressBytes))
}
/*
&omitempty.coordinate{Lat:1, Lng:0}
{
"latitude": 1
}
*/

这个坐标的longitude消失不见了!

但我们的设想是,如果一个地点没有经纬度信息,则悬空,这没有问题,但对于“原点坐标”,我们在确切知道它的经纬度的情况下,(0.0, 0.0)仍然被忽略了。正确的写法也是将结构体内的定义改为指针

1
2
3
4
5
6
7
8
9
10
11
type coordinate struct {
Lat *float64 `json:"latitude,omitempty"`
Lng *float64 `json:"longitude,omitempty"`
}
/*
&omitempty.coordinate{Lat:(*float64)(0xc0000a6288), Lng:(*float64)(0xc0000a6298)}
{
"latitude": 1,
"longitude": 0
}
*/

这样空值就从 float64 的 0.0 变为了指针类型的 nil ,我们就能看到正确的经纬度输出。


 评论