1. 程式人生 > 其它 >Golang 結構體轉換中的omitempty標籤

Golang 結構體轉換中的omitempty標籤

個人學習總結,如有不對的地方也歡迎指出,一起交流學習,共同進步

結構體轉Json

type Person1 struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Addr string `json:"addr"`
}
type Person2 struct {
	Name string `json:"name"`
	Age  int    `json:"age,omitempty"`
	Addr string `json:"addr"`
}

func main() {
	var ps Person1
	//var ps Person2
	ps.Name = "ming"
	ps.Addr = "Beijing"
	psByte, _ := json.Marshal(ps)
	fmt.Printf("%s\n", string(psByte))
}

上面兩個結構體的Age欄位分別不帶和帶有omitempty標籤,如果Age欄位都賦值為零值 ,則各自生成的json物件分別對應下面兩個結果

Person1: {"name":"ming","age":0,"addr":"Beijing"}
Person2: {"name":"ming","addr":"Beijing"}

Person2相較於Person1得到的json物件中沒有"age"鍵,原因就在於 omitempty標籤會在json資料結構轉換時,忽略掉了對應型別為零值的欄位。

Json轉結構體

和結構體轉Json物件不同,omitempty並不約束或者說無法約束json物件轉結構體,無論有沒有新增這個標籤,轉換得到結構體的所有成員都是可以訪問的(前提是成員首字母是大寫的)且有值的。

比如說同樣是上面的結構體Person1和Person2,當我們用下面的json物件分別去轉換成這兩個結構體時,會發現它們倆的內容是完全一樣的,omitempty並不會影響其結果,這是因為在json.Unmarshal前必須要先初始化出一個結構體物件(即這裡的pst1或pst2)才能去承接json物件的值,而結構體物件在預設初始化時每個成員就已經被賦予了一個對應型別的零值了,所以即使結構體內定義的成員找不到json物件中對應的鍵,每個成員也都是有值的(雖然這就會導致結構體和json物件間可能並不是完全對應的)。

Person: {"name": "ming", "addr": "Beijing"}

既然上面提到,當結構體內定義的成員找不到json物件中對應的鍵,那麼這個結構體成員會被預設賦予一個零值,並不受omitempty影響。那一個由json物件轉換得到的結構體物件中的某個成員是零值,如何去判斷是原來的json到底有沒有這個鍵呢?

這時候可以把這個欄位定義成對應的指標型別,如下面結構體Person3的Addr欄位

type Person3 struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Addr *string `json:"addr,omitempty"`
}

雖然json物件不管有沒有"addr"這個鍵,得到的Person3物件都能夠訪問Addr欄位,但可以通過比較Addr是否等於nil來判斷json物件存不存在對應鍵,如果等於nil 則說明不存在,反之則存在。這裡其實是利用了指標的零值為nil的特性。

新增omitempty的時機

對於一個指標型別的欄位,無論是否新增omitempty欄位,在不給該欄位傳值的情況,預設會被賦予一個空指標,依舊可以對這個結構體物件的該欄位判斷是否為空

其實就是對於一個結構體的欄位而言,是否新增該欄位只會影響到該結構體物件轉化為json物件,添加了該欄位,而恰好建立這個結構體物件時這個欄位又沒有傳入值,那這個欄位就會預設被賦予一個零值,然後再轉換為json物件的時候這個欄位就會被省略掉,使得展開jso物件的時候是看不到這個欄位的。但並不會影響到這個結構體物件的使用,結構體的這個欄位就是一個零值,類比的你可以想象建立一個空結構體物件的時候,那麼所有欄位都初始化為對應型別的零值了

下面只考慮結構體轉Json的場景
如果欄位是int型別,通常都要加上omitempty,因為你不希望明明沒給欄位賦值生成的json物件還會有這個對應鍵。同時,要將int轉換為*int,否則就會因為賦值也為卻被忽略掉的情況,除非這個欄位的不存在值為的情況
如果是string型別,沒給這個欄位傳值也沒關係,因為本身string的零值也是(空),有沒有加上omitempty關鍵字生成json物件的結果都是一樣的。但如果""(空字串)對於該欄位也具有某種含義的話,則需要將string型別轉為*string(指標)型別
如果是結構體,需要分兩種情況來看,一種是匿名結構體,另一種是有名結構體。

type Book struct {
	Name   string  `json:"name"`
	Price  float32 `json:"price"`
	Desc   string  `json:"desc,omltempty"`
	Author  //匿名結構體
}
type Book2 struct {
	Name   string  `json:"name"`
	Price  float32 `json:"price"`
	Desc   string  `json:"desc,omitempty"`
	Author Author  `json:"author,omitempty"` //有名結構體
}
type Book3 struct {
	Name   string  `json:"name"`
	Price  float32 `json:"price"`
	Desc   string  `json:"desc,omitempty"`
	Author *Author `json:"author,omitempty"` //有名結構體(指標型別)
}
type Author struct {
	Gender int `json:"gender,omitempty"`
	Age    int `json:"age,Omitempty"`
}                                                

1)匿名結構體本質上更接近外層結構體的欄位的一個集合,並不算結構體巢狀。

func main() {
	 var book Book 
	 book.Name = "testBook" 
	 bookByte, _ := json.Marshal(book) 
	 fmt.Printf("%s\n", string (bookByte)) 
}

對於上面的定義,在Gender和Age都沒有賦值的情況下,輸出的結果下面這樣,但要注意的是,被忽略掉其實是"gender"和"age'"兩個鍵而非"author"這個鍵,只要給Gender或者Age欄位賦值,"gender"或"age"鍵就會顯示出來,且和"name"、"price" 平級。

{"name":"testBook","price":0}

2)而有名結構體依舊可以按照兩種方式去討論,即原型別和指標型別

func main() {
	 var book Book 
	 book.Name = "testBook" 
	 bookByte, _ := json.Marshal(book) 
	 fmt.Printf("%s\n", string (bookByte)) 
}

比如原型別結果是這樣

{"name":"testBook","price":0,"author":{}}

但指標型別的結果就會忽略掉"author"鍵

{"name":"testBook","price":0}

產生這種差別的原因就在於編譯器如何處理某一種型別的零值。無論是int、 string、interface、*ptr還是slice、map型別的欄位,當沒有顯式初始化時, 編譯器都能預設地給設定一個相應型別的零值,而恰好該欄位又有omitempty關鍵字時,轉換到json物件時就會因為這個欄位為一個零值忽略掉該欄位。
而結構體型別比較特殊,以上面的定義為例,Book2.Author沒有顯示初始化時, Book2.Author內部所有欄位會遞迴地初始化為各個型別的零值,即結構體的零值為 內部欄位均為零值的結構體物件,而在轉換的時候,會將整個Book2.Author欄位轉 換為一個空的巢狀json物件(Gender和Age欄位都被忽略掉了),鍵名則為 "author"。如果希望忽略掉這個鍵,便需要將Book2.Author的型別變成對應的結構體指標,因為這樣就可以按照指標的零值去處理了

總結來說就是下面幾點

1)結構體轉json時,如果希望某個欄位不顯式初始化(賦值)就不會存在對應的鍵,則加上0mitempty關鍵字

2)如果已經加上了omitempty關鍵字,但顯示初始化(賦值)這個欄位的資料又可能為該型別的零值,則需要考慮將這個欄位轉成對應的指標型別去處理,否則這 個欄位即便傳入了值,最後仍然會因為是零值而被忽略

3) json轉結構體時,某個欄位的值是零值(如int型別的欄位數值為0),如果需要確定json物件是否存在這樣一個鍵,則可以將這個欄位轉成對應的指標型別去處理