Tags in Golang

土二寸發表於2018-06-27
type People struct {
	Name string `json:"name"`
	Age  int8   `json:"age"`
}
複製程式碼

在學習過程中,看到類似上面的程式碼,一下子懵了個逼?。。。大概一查,這是 Golang 中的 Tags 語法,官方解釋是這樣的:

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. The tags are made visible through a reflection interface but are otherwise ignored.

官方的解釋不夠接地氣,像我這樣的初學者看了等於是沒看的,我們加點實際場景進去就能明白這到底能幹嗎。 由於 Golang 中對欄位的標記可以在 反射 時獲取到,所以通常是用來在將 struct 編碼轉換的過程中提供一些轉換規則的資訊,比如?下面例子中提到的 JSON 轉換。當然你也可以用它來儲存你想要的任何其他元資訊(Meta-information)。

舉例子

舉一個 API 的例子 ?,獲取使用者ID為1的使用者資訊:GET /v1/user/1。簡單的做法是在獲取到資料庫 user 表的一條記錄後,以 JSON 格式返回。

type User struct {
	Id int
  Name string
  Age int8
}
複製程式碼

如果不做任何處理,那麼此時返回的資料應該是如下:

{"Id":1,"Name":"X.FLY","Age":24}
複製程式碼

咦,為啥都給我返回駝峰型的屬性名啊?誰讓你宣告 User 結構體的時候屬性名就是這樣呢。 那麼把結構體屬性名改成小寫吧。? 不行?,因為這樣這些屬性就都變成了私有屬性了,別忘了 Golang 裡面可沒有 public / private / protected 這些關鍵詞,全靠首字母大小寫來區分好不! 那咋辦?用 Tag 啊 ?

我們為結構體屬性加上標記:

type User struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
	Age  int8
}
複製程式碼

故意不給 age 加標記以便看區別,JSON 格式輸出如下:

{"id":1,"name":"X.FLY","Age":24}
複製程式碼

這就是 Tag 的神奇之處,不會汙染到主體,也不需要在每次輸出 JSON 資料的時候人工處理(好吧,移除 public 等關鍵詞是需要付出代價的?)。

使用方法

Tag 可以是任何字串,下面將的是常見形式,別想岔了?

一般來講,Tag 都是以 key:"value" 這種鍵值對的形式,如果有多個鍵值對,則以空格分隔。

type User struct {
	Name string `json:"name" xml:"name"`
}
複製程式碼

key 一般指的是要使用的包名,比如這裡的 json 表示這個 Name 欄位會被 encoding/json 包使用和處理。

如果有多個 value 資訊要傳入,那麼通常使用英文逗號 , 進行分隔。

type User struct {
	Name string `json:"name,omitempty" xml:"name"`
}
複製程式碼

omitempty 表明如果這個欄位的值在編解碼時為空(Defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string)那麼就忽略這個欄位。還有一個常用的是 -,它表示直接忽略這個欄位。

理解 Tag

前面說到 Tag 可以被 reflect 包獲取到,其實除了通過反射機制能獲取到 Tag 資訊,對於其他方式這些標記都是不可見的! 我們先來看看官方 reflect 中 StructTag 部分,reflect - The Go Programming Language

StructTagstring 基本型別的別名:type StructTag string,約定俗成的規則是以 key:"value" 這樣的鍵值對。(如果我不按照這種 約定 來做呢?當然也可以,但是這樣做會導致 StructTag.Get() 方法解析不了你那脫俗的標記,那麼你就只能自己實現自己的解析邏輯了,總之你高興就好?)

func (tag StructTag) Get(key string) string
複製程式碼

Get() 函式用於獲取指定 key 的 value,比如有一個標記是 mytag:"X.FLY",那麼 Get("mytag") => X.FLY

func (tag StructTag) Lookup(key string) (value string, ok bool)
複製程式碼

Lookup() 函式是在 Golang 1.7 之後加的,用來判斷是否存在指定的 key。

使用例子可以在 The Go Playground 上執行嘗試,這裡就對反射不多做展開了。

JSON’s Tag

這裡著重舉出 JSON 的 Struct Tag(1. JSON 輸出很常見; 2. 可以以此類推其他如 XML’s Tag)。 我想知道 JSON 的 tag 有哪些,去哪找?去官網 JSON.Marshal 函式文件中找。

The encoding of each struct field can be customized by the format string stored under the "json" key in the struct field's tag. The format string gives the name of the field, possibly followed by a comma-separated list of options. The name may be empty in order to specify options without overriding the default field name. 我們會發現,在 JSON 編碼過程中會去獲取每一個 Struct field 的標記,從中拿取 key 為 json 的值,然後進行相應處理。

注意解析規則:value 的第一個字串一定表示覆蓋後的新欄位名,後面如果有解析選項,則以英文逗號分隔。

比如 Name string json:"name,omitempty",第一個字串 name 表示在編碼後 Name 屬性名就變成了 name。然後緊跟逗號分隔符,接著是 omitempty 選項。

  1. 如果我不想覆蓋,只想加選項怎麼辦?Name string json:",omitempty",直接英文逗號打頭。
  2. 極端一點,如果我的欄位名就叫 Omitempty 呢?Omitempty string json:"omitempty,omitempty",記住第一個字串表示的是新變數名,而不是選項,所以重名就重名好了,不怕?。

思考一下:- string json:"-,"- string json:",-" 有什麼區別??

  1. omitempty:如果欄位的值為空(Defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string),那麼在編碼過程中就忽略掉這個欄位。
  2. -:二話不說直接忽略該欄位。
  3. string:將欄位值在編碼過程中轉換成 JSON 中的字串型別,只有當欄位型別是 string, floating point, integer, or boolean 的情況下才會轉換。

其他 Tag

常用的一些 Struct Tag 官方有列舉,Well known struct tags · golang/go Wiki · GitHub

參考連結

相關文章