golang omitempty 總結
在使用Golang的時候,不免會使用Json和結構體的相互轉換,這時候常用的就是 json.Marshal和json.Unmarshal兩個函式。
這時候在定義json結構體的時候,我們會用到omitempty這個欄位,這個欄位看似簡單,但是卻有很多小坑,這篇文章帶你稍微研究一下他的用途和功能
Basic Usage
當我們設定json的struct的時候,會定義每個欄位對一個json的格式,比如定義一個dog 結構體:
type Dog struct {
Breed string
WeightKg int
}
現在我們對他進行初始化,將其編碼為JSON格式:
func main() {
d := Dog{
Breed: "dalmation",
WeightKg: 45,
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
則輸出的結果為:{"Breed":"dalmation","WeightKg":45},你可點選這裡.
現在假如有一個結構體變數我們沒初始化,那麼結果可能也會跟我們預期的不太一樣:
func main() {
d := Dog{
Breed: "pug",
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
輸出的結果為:{"Breed":"pug","WeightKg":0},明顯dog的weight是未知,而不是0,並不是我們想要的結果,我們更想要的結果是:"WeightKg":null。
為了實現這樣的目的,我們這時候應該使用omitempty 變數來幫我們實現,當我們在Dog結構體加上這個tag的時候:
type Dog struct {
Breed string
// The first comma below is to separate the name tag from the omitempty tag
WeightKg int `json:",omitempty"`
}
輸出結果為:{"Breed":"pug"}。現在WeightKg就被設定為預設零值(對於int應該為0,對於string應該為"", 指標的話應該為nil)。
不能單純使用omitted
當結構體相互巢狀的時候,那麼omitempty就可能出現問題,比如:
type dimension struct {
Height int
Width int
}
type Dog struct {
Breed string
WeightKg int
Size dimension `json:",omitempty"`
}
func main() {
d := Dog{
Breed: "pug",
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
輸出結果為:
{"Breed":"pug","WeightKg":0,"Size":{"Height":0,"Width":0}}
我們已經使用omitempty標註的dimension還是顯示了出來。這是因為結構體dimension不知道空值是什麼,GO只知道簡單結構體例如int,string,pointer 這種型別的空值,為了不顯示我們沒有提供值的自定義結構體,我們可以使用結構體指標:
type Dog struct {
Breed string
WeightKg int
// Now `Size` is a pointer to a `dimension` instance
Size *dimension `json:",omitempty"`
}
執行結果為:
{"Breed":"pug","WeightKg":0}
為什麼會這樣呢?因為指標是基本型別啊,Golang知道他的空值是啥,所以就直接賦值為nil(指標型別的空值)。
現在出一個問題,下面的程式的初始結果是什麼?
type Dog struct {
Age *int `json:",omitempty"`
}
func main() {
age := 0
d := Dog{
Age: &age,
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
- A. {"Age":0}
- B. {}
答案在最後揭曉。
0, "", nil 三者的區別
現在很難的很區別的是:零值,值為0,預設值。
比如我們設定一個restaurant結構體:
type Restaurant struct {
NumberOfCustomers int `json:",omitempty"`
}
func main() {
d := Restaurant{
NumberOfCustomers: 0,
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
他的輸出結果為:
{}
這肯定不是我們想要的結果,因為這個時候我們想要他輸出0,他卻沒有任何值輸出。因為Golang把0當成了零值,所以跟沒有賦值是一樣的,比如:
type Restaurant struct {
NumberOfCustomers int `json:",omitempty"`
Name string
}
func main() {
d := Restaurant{
//給NumberOfCustomers賦值
NumberOfCustomers: 0,
Name: "hello",
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
和
type Restaurant struct {
NumberOfCustomers int `json:",omitempty"`
Name string
}
func main() {
d := Restaurant{
//未給NumberOfCustomers賦值
Name: "hello",
}
b, _ := json.Marshal(d)
fmt.Println(string(b))
}
二者結果都是:{"Name":"hello"}
解決這樣的問題的一種方法是使用int指標,因為int指標的空值為nil,當我想輸出0的時候,我傳進去地址,地址肯定不是空值nil,這樣肯定會顯示出來0.
type Restaurant struct {
NumberOfCustomers *int `json:",omitempty"`
}
func main() {
d1 := Restaurant{}
b, _ := json.Marshal(d1)
fmt.Println(string(b))
//Prints: {}
n := 0
d2 := Restaurant{
NumberOfCustomers: &n,
}
b, _ = json.Marshal(d2)
fmt.Println(string(b))
//Prints: {"NumberOfCustomers":0}
}
基於上,我們應該謹慎使用omitempty,如果選擇使用了他,那你就要第一時間知道,各個值的空值是什麼?當我沒有給某個變數賦值的時候,他應該是什麼樣的,我想要什麼的輸出?這都是你要仔細斟酌的。
好了,現在公佈答案:A:因為他是int型別的指標,我們傳進去的也是指標,所以不會有任何問題。同時&age不是指標的nil值,所以不會被忽略,顯示的時候不會有問題,就是0.
ref: https://www.sohamkamani.com/golang/2018-07-19-golang-omitempty/; https://golang.org/pkg/encoding/json/