Go 語言中結(jié)構(gòu)體打 Tag 是什么意思?
前言
哈嘍,大家好,我是asong。今天想與大家分享Go語言中結(jié)構(gòu)體標(biāo)簽是怎么使用的,以及怎樣定制自己的結(jié)構(gòu)體標(biāo)簽解析。
大多數(shù)初學(xué)者在看公司的項(xiàng)目代碼時,看到的一些結(jié)構(gòu)體定義會是這樣的:
- type Location struct {
- Longitude float32 `json:"lon,omitempty"`
- Latitude float32 `json:"lat,omitempty"`
- }
字段后面會有一個標(biāo)簽,這個標(biāo)簽有什么用呢?
上面的例子中,標(biāo)簽json:"lon,omitempty"代表的意思是結(jié)構(gòu)體字段的值編碼為json對象時,每一個導(dǎo)出字段變成該對象的一個成員,這個成員的名字為lon或者lat,并且當(dāng)字段是空值時,不導(dǎo)出該字段;總結(jié)就是lon、lat是重命名成員的名字,omitempty用來決定成員是否導(dǎo)出。
看到這里,有一些朋友可能會好奇,這個你是怎么知道這樣使用的呢?我可以隨便寫標(biāo)簽嗎?
接下來我們就一點(diǎn)點(diǎn)來揭秘,開車!!!
什么是標(biāo)簽
Go語言提供了可通過反射發(fā)現(xiàn)的的結(jié)構(gòu)體標(biāo)簽,這些在標(biāo)準(zhǔn)庫json/xml中得到了廣泛的使用,orm框架也支持了結(jié)構(gòu)體標(biāo)簽,上面那個例子的使用就是因?yàn)閑ncoding/json支持了結(jié)構(gòu)體標(biāo)簽,不過他有自己的標(biāo)簽規(guī)則;但是他們都有一個總體規(guī)則,這個規(guī)則是不能更改的,具體格式如下:
- `key1:"value1" key2:"value2" key3:"value3"...` // 鍵值對用空格分隔
結(jié)構(gòu)體標(biāo)簽可以有多個鍵值對,鍵與值要用冒號分隔,值要使用雙引號括起來,多個鍵值對之間要使用一個空格分隔,千萬不要使用逗號!!!
如果我們想要在一個值中傳遞多個信息怎么辦?不同庫中實(shí)現(xiàn)的是不一樣的,在encoding/json中,多值使用逗號分隔:
- `json:"lon,omitempty"`
在gorm中,多值使用分號分隔:
- `gorm:"column:id;primaryKey"
具體使用什么符號分隔需要大家要看各自庫的文檔獲取。
結(jié)構(gòu)體標(biāo)簽是在編譯階段就和成員進(jìn)行關(guān)聯(lián)的,以字符串的形式進(jìn)行關(guān)聯(lián),在運(yùn)行階段可以通過反射讀取出來。
現(xiàn)在大家已經(jīng)知道什么是結(jié)構(gòu)體標(biāo)簽了,規(guī)則還是很規(guī)范的,但是很容易出錯,因?yàn)镚o語言在編譯階段并不會對其格式做合法鍵值對的檢查,這樣我們不小心寫錯了,就很難被發(fā)現(xiàn),不過我們有g(shù)o vet工具做檢查,具體使用來看一個例子:
- type User struct {
- Name string `abc def ghk`
- Age uint16 `123: 232`
- }
- func main() {
- }
然后執(zhí)行g(shù)o vet main.go,得出執(zhí)行結(jié)果:
- # 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值語法錯誤。
所以在我們項(xiàng)目中引入go vet作為CI檢查是很有必要的。
標(biāo)簽使用場景
Go官方已經(jīng)幫忙整理了哪些庫已經(jīng)支持了struct tag:https://github.com/golang/go/wiki/Well-known-struct-tags。
Tag | Documentation |
---|---|
xml | https://godoc.org/encoding/xml |
json | https://godoc.org/encoding/json |
asn1 | https://godoc.org/encoding/asn1 |
reform | https://godoc.org/gopkg.in/reform.v1 |
dynamodb | https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/dynamodbattribute/#Marshal |
bigquery | https://godoc.org/cloud.google.com/go/bigquery |
datastore | https://godoc.org/cloud.google.com/go/datastore |
spanner | https://godoc.org/cloud.google.com/go/spanner |
bson | https://godoc.org/labix.org/v2/mgo/bson, https://godoc.org/go.mongodb.org/mongo-driver/bson/bsoncodec |
gorm | https://godoc.org/github.com/jinzhu/gorm |
yaml | https://godoc.org/gopkg.in/yaml.v2 |
toml | https://godoc.org/github.com/pelletier/go-toml |
validate | https://github.com/go-playground/validator |
mapstructure | https://godoc.org/github.com/mitchellh/mapstructure |
parser | https://godoc.org/github.com/alecthomas/participle |
protobuf | https://github.com/golang/protobuf |
db | https://github.com/jmoiron/sqlx |
url | https://github.com/google/go-querystring |
feature | https://github.com/nikolaydubina/go-featureprocessing |
像json、yaml、gorm、validate、mapstructure、protobuf這幾個庫的結(jié)構(gòu)體標(biāo)簽是很常用的,gin框架就集成了validate庫用來做參數(shù)校驗(yàn),方便了許多,之前寫了一篇關(guān)于validate的文章:boss: 這小子還不會使用validator庫進(jìn)行數(shù)據(jù)校驗(yàn),開了~~~,可以關(guān)注一下。
具體這些庫中是怎么使用的,大家可以看官方文檔介紹,寫的都很詳細(xì),具體場景具體使用哈!!!
自定義結(jié)構(gòu)體標(biāo)簽
現(xiàn)在我們可以回答開頭的一個問題了,結(jié)構(gòu)體標(biāo)簽是可以隨意寫的,只要符合語法規(guī)則,任意寫都可以的,但是一些庫沒有支持該標(biāo)簽的情況下,隨意寫的標(biāo)簽是沒有任何意義的,如果想要我們的標(biāo)簽變得有意義,就需要我們提供解析方法??梢酝ㄟ^反射的方式獲取標(biāo)簽,所以我們就來看一個例子,如何使用反射獲取到自定義的結(jié)構(gòu)體標(biāo)簽。
- 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)
- }
運(yùn)行結(jié)果如下:
- get tag is Username
- get tag is age
- get tag is min=6,max=10
這里我們使用TypeOf方法獲取的結(jié)構(gòu)體類型,然后去遍歷字段,每個字段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是一個內(nèi)置類型,提供了Get、Loopup兩種方法來解析標(biāo)簽中的值并返回指定鍵的值:
- func (tag StructTag) Get(key string) string
- func (tag StructTag) Lookup(key string) (value string, ok bool)
Get內(nèi)部也是調(diào)用的Lookup方法。區(qū)別在于Lookup會通過返回值告知給定key是否存在與標(biāo)簽中,Get方法完全忽略了這個判斷。
總結(jié)
本文主要介紹一下Go語言中的結(jié)構(gòu)體標(biāo)簽是什么,以及如何使用反射獲取到解結(jié)構(gòu)體標(biāo)簽,在日常開發(fā)中我們更多的是使用一些庫提供好的標(biāo)簽,很少自己開發(fā)使用,不過大家有興趣的話可以讀一下validae的源碼,看看他是如何解析結(jié)構(gòu)體中的tag,也可以自己動手實(shí)現(xiàn)一個校驗(yàn)庫,當(dāng)作練手項(xiàng)目。
文中代碼已上傳github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/struct_tag_demo