Golang 中的標籤(Tags in Golang)
結構體欄位的宣告可以通過之後放置的文字來標記。標籤新增由當前包或外部包使用的元資訊。讓我們首先回首一下 strcut 宣告的樣子,然後我們將扔出幾個用例,深入研究這個標籤。
結構體型別(Struct type)
Struct 是一系列欄位。每個欄位由可選名稱和所需型別(原始碼 )組成:
package main import "fmt" type T1 struct { f1 string } type T2 struct { T1 f2int64 f3, f4 float64 } func main() { t := T2{T1{"foo"}, 1, 2, 3} fmt.Println(t.f1)// foo fmt.Println(t.T1.f1) // foo fmt.Println(t.f2)// 1 }
T1 域被稱為嵌入欄位,因為它是用型別宣告但沒有名稱。
欄位宣告可以在 T2 中指定來自第 3 個欄位宣告的 f3 和 f4 之類的多個識別符號。
語言規範宣告每個欄位聲明後面跟著分號,但正如我們上面所見,它可以省略。如果需要將多個欄位宣告放入同一行(原始碼),分號可能很有用(原始碼 ):
package main import "fmt" type T struct { f1 int64; f2 float64 } func main() { t := T{1, 2} fmt.Println(t.f1, t.f2)// 1 2 }
標籤(Tag)
欄位聲明後面可以跟一個可選的字串文字(標記),它稱為相應欄位宣告中所有欄位的屬性(單欄位宣告可以指定多個識別符號)。讓我們看看它的實際應用(原始碼 ):
type T struct { f1string "f one" f2string f3string `f three` f4, f5 int64`f four and five` }
可以使用原始字串文字或解釋的字串文字,但下面描述的傳統格式需要原始字串文字。規範 中描述了原始字串文字和解釋字串文字之間的差異。
如果欄位宣告包含多個識別符號,則標記將附加到欄位宣告的所有欄位(如上面的欄位 f4 和 f5)。
反射(Reflection)
標籤可通過 reflect 包訪問,允許執行時反射(原始碼 ):
package main import ( "fmt" "reflect" ) type T struct { f1string "f one" f2string f3string `f three` f4, f5 int64`f four and five` } func main() { t := reflect.TypeOf(T{}) f1, _ := t.FieldByName("f1") fmt.Println(f1.Tag) // f one f4, _ := t.FieldByName("f4") fmt.Println(f4.Tag) // f four and five f5, _ := t.FieldByName("f5") fmt.Println(f5.Tag) // f four and five }
設定空標記與完全不使用標記的效果相同(原始碼 ):
type T struct { f1 string `` f2 string } func main() { t := reflect.TypeOf(T{}) f1, _ := t.FieldByName("f1") fmt.Printf("%q\n", f1.Tag) // "" f2, _ := t.FieldByName("f2") fmt.Printf("%q\n", f2.Tag) // "" }
慣用格式(Conventional format)
在提交中引入“反射:支援多個包使用結構標記”
允許為每個包設定元資訊。這提供了簡單的名稱空間。標籤被格式化為鍵的串聯:“值”對。金鑰可能是像 JSON 這樣的包的名稱。對可以選擇用空格分隔 -key1: "value1" key2: "value2" key3: "value3"
。 如果使用傳統格式,那麼我們可以使用 struct tag(StructTag
)的兩個方法 - Get 或 Lookup。它們允許返回與所需鍵內部標記相關聯的值。
Lookup 函式返回兩個值 - 與鍵關聯的值(如果未設定則為空)和 bool,指示是否已找到鍵(原始碼 ):
type T struct { f string `one:"1" two:"2"blank:""` } func main() { t := reflect.TypeOf(T{}) f, _ := t.FieldByName("f") fmt.Println(f.Tag) // one:"1" two:"2"blank:"" v, ok := f.Tag.Lookup("one") fmt.Printf("%s, %t\n", v, ok) // 1, true v, ok = f.Tag.Lookup("blank") fmt.Printf("%s, %t\n", v, ok) // , true v, ok = f.Tag.Lookup("five") fmt.Printf("%s, %t\n", v, ok) // , false }
Get 方法只是 Lookup 簡單的包封裝器,它丟棄了 bool 值(原始碼 ):
func (tag StructTag) Get(key string) string { v, _ := tag.Lookup(key) return v }
如果標籤不是常規模式,則不指定 Get 或 Lookup 的返回值。
即使 tag 是任何字串值(不管是釋義或原始值),只有在雙引號(原始碼 )之間包含值時,Lookup 和 Get 方法才會找到 key 的值:
type T struct { f string "one:`1`" } func main() { t := reflect.TypeOf(T{}) f, _ := t.FieldByName("f") fmt.Println(f.Tag) // one:`1` v, ok := f.Tag.Lookup("one") fmt.Printf("%s, %t\n", v, ok) // , false }
可以在解釋的字串值中對雙引號進行轉義(原始碼 ):
type T struct { f string "one:\"1\"" } func main() { t := reflect.TypeOf(T{}) f, _ := t.FieldByName("f") fmt.Println(f.Tag) // one:"1" v, ok := f.Tag.Lookup("one") fmt.Printf("%s, %t\n", v, ok) // 1, true }
但可讀性就要低很多。
結論(Conversion)
將結構體型別轉換為其他型別要求底層型別相同,但忽略掉 tag(原始碼 ):
type T1 struct { f int `json:"foo"` } type T2 struct { f int `json:"bar"` } t1 := T1{10} var t2 T2 t2 = T2(t1) fmt.Println(t2) // {10}
Go 1.8 (提案)中引入了此行為。在 Go 1.7 及更早版本的程式碼中,可能會丟擲編譯時錯誤。
用例(Use cases)
(Un)marshaling
Go 中標籤最常見的用途可能是marshalling 。讓我們看一下來自 JSON 包的函式Marshal 如何使用它(原始碼 ):
import ( "encoding/json" "fmt" ) func main() { type T struct { F1 int `json:"f_1"` F2 int `json:"f_2,omitempty"` F3 int `json:"f_3,omitempty"` F4 int `json:"-"` } t := T{1, 0, 2, 3} b, err := JSON.Marshal(t) if err != nil { panic(err) } fmt.Printf("%s\n", b) // {"f_1":1,"f_3":2} }
xml 包也利用了標籤 -https://golang.org/pkg/encoding/xml/#MarshalIndent .
ORM
像 GORM 這樣的物件關係對映工具,也廣泛使用標籤 -例子 .
摘要資料(Digesting forms data)
https://godoc.org/github.com/gorilla/schema
其他(Other)
標籤的更多潛在用例,如配置管理,結構的預設值,驗證,命令列引數描述等(眾所周知的結構標記列表 )。
Go vet
Go 編譯器沒有強制執行傳統的 struct 標籤格式,但是 vet 就是這樣做的,所以值得使用它,例如作為 CI 管道的一部分。
package main type T struct { f string "one two three" } func main() {} > Go vet tags.go tags.go:4: struct field tag `one two three` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair
...
由於 struct 標籤,程式設計師可以從單一來源中受益。Go 是一門實用性語言,所以即使可以使用專用資料結構等其他方式來控制整個過程來解決 JSON/XML 編碼,Golang 也能讓軟體工程師的生活變得更輕鬆。值得一提的是,標籤的長度不受規格的限制。