原文連結:Go語言中結構體打Tag是什麼意思?
前言
哈嘍,大家好,我是asong
。今天想與大家分享Go
語言中結構體標籤是怎麼使用的,以及怎樣定製自己的結構體標籤解析。
大多數初學者在看公司的專案程式碼時,看到的一些結構體定義會是這樣的:
type Location struct {
Longitude float32 `json:"lon,omitempty"`
Latitude float32 `json:"lat,omitempty"`
}
欄位後面會有一個標籤,這個標籤有什麼用呢?
上面的例子中,標籤json:"lon,omitempty"
代表的意思是結構體欄位的值編碼為json
物件時,每一個匯出欄位變成該物件的一個成員,這個成員的名字為lon
或者lat
,並且當欄位是空值時,不匯出該欄位;總結就是lon
、lat
是重新命名成員的名字,omitempty
用來決定成員是否匯出。
看到這裡,有一些朋友可能會好奇,這個你是怎麼知道這樣使用的呢?我可以隨便寫標籤嗎?
接下來我們就一點點來揭祕,開車!!!
什麼是標籤
Go
語言提供了可通過反射發現的的結構體標籤,這些在標準庫json/xml
中得到了廣泛的使用,orm
框架也支援了結構體標籤,上面那個例子的使用就是因為encoding/json
支援了結構體標籤,不過他有自己的標籤規則;但是他們都有一個總體規則,這個規則是不能更改的,具體格式如下:
`key1:"value1" key2:"value2" key3:"value3"...` // 鍵值對用空格分隔
結構體標籤可以有多個鍵值對,鍵與值要用冒號分隔,值要使用雙引號括起來,多個鍵值對之間要使用一個空格分隔,千萬不要使用逗號!!!
如果我們想要在一個值中傳遞多個資訊怎麼辦?不同庫中實現的是不一樣的,在encoding/json
中,多值使用逗號分隔:
`json:"lon,omitempty"`
在gorm
中,多值使用分號分隔:
`gorm:"column:id;primaryKey"
具體使用什麼符號分隔需要大家要看各自庫的文件獲取。
結構體標籤是在編譯階段就和成員進行關聯的,以字串的形式進行關聯,在執行階段可以通過反射讀取出來。
現在大家已經知道什麼是結構體標籤了,規則還是很規範的,但是很容易出錯,因為Go語言在編譯階段並不會對其格式做合法鍵值對的檢查,這樣我們不小心寫錯了,就很難被發現,不過我們有go vet
工具做檢查,具體使用來看一個例子:
type User struct {
Name string `abc def ghk`
Age uint16 `123: 232`
}
func main() {
}
然後執行go vet main.go
,得出執行結果:
# command-line-arguments
go_vet_tag/main.go:4:2: struct field tag `abc def ghk` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair
go_vet_tag/main.go:5:2: struct field tag `123: 232` not compatible with reflect.StructTag.Get: bad syntax for struct tag value
bad syntax for struct tag pair
告訴我們鍵值對語法錯誤,bad syntax for struct tag value
值語法錯誤。
所以在我們專案中引入go vet
作為CI
檢查是很有必要的。
標籤使用場景
Go
官方已經幫忙整理了哪些庫已經支援了struct tag
:https://github.com/golang/go/...。
像json
、yaml
、gorm
、validate
、mapstructure
、protobuf
這幾個庫的結構體標籤是很常用的,gin
框架就整合了validate
庫用來做引數校驗,方便了許多,之前寫了一篇關於validate
的文章:boss: 這小子還不會使用validator庫進行資料校驗,開了~~~,可以關注一下。
具體這些庫中是怎麼使用的,大家可以看官方文件介紹,寫的都很詳細,具體場景具體使用哈!!!
自定義結構體標籤
現在我們可以回答開頭的一個問題了,結構體標籤是可以隨意寫的,只要符合語法規則,任意寫都可以的,但是一些庫沒有支援該標籤的情況下,隨意寫的標籤是沒有任何意義的,如果想要我們的標籤變得有意義,就需要我們提供解析方法。可以通過反射的方式獲取標籤,所以我們就來看一個例子,如何使用反射獲取到自定義的結構體標籤。
type User struct {
Name string `asong:"Username"`
Age uint16 `asong:"age"`
Password string `asong:"min=6,max=10"`
}
func getTag(u User) {
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("asong")
fmt.Println("get tag is ", tag)
}
}
func main() {
u := User{
Name: "asong",
Age: 5,
Password: "123456",
}
getTag(u)
}
執行結果如下:
get tag is Username
get tag is age
get tag is min=6,max=10
這裡我們使用TypeOf
方法獲取的結構體型別,然後去遍歷欄位,每個欄位StructField
都有成員變數Tag
:
// A StructField describes a single field in a struct.
type StructField struct {
Name string
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
Tag
是一個內建型別,提供了Get
、Loopup
兩種方法來解析標籤中的值並返回指定鍵的值:
func (tag StructTag) Get(key string) string
func (tag StructTag) Lookup(key string) (value string, ok bool)
Get
內部也是呼叫的Lookup
方法。區別在於Lookup
會通過返回值告知給定key
是否存在與標籤中,Get
方法完全忽略了這個判斷。
總結
本文主要介紹一下Go
語言中的結構體標籤是什麼,以及如何使用反射獲取到解結構體標籤,在日常開發中我們更多的是使用一些庫提供好的標籤,很少自己開發使用,不過大家有興趣的話可以讀一下validae
的原始碼,看看他是如何解析結構體中的tag
,也可以自己動手實現一個校驗庫,當作練手專案。
文中程式碼已上傳github
:https://github.com/asong2020/...
好啦,本文到這裡就結束了,我是asong
,我們下期見。
歡迎關注公眾號:Golang夢工廠