golang omitempty 總結

HugeYuan發表於2020-12-10

golang omitempty 總結

在使用Golang的時候,不免會使用Json和結構體的相互轉換,這時候常用的就是 json.Marshaljson.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/

相關文章