Go 語言中結(jié)構(gòu)體打 Tag 是什么意思?
前言
哈嘍,大家好,我是asong。今天想與大家分享Go語言中結(jié)構(gòu)體標(biāo)簽是怎么使用的,以及怎樣定制自己的結(jié)構(gòu)體標(biāo)簽解析。
大多數(shù)初學(xué)者在看公司的項目代碼時,看到的一些結(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)簽,上面那個例子的使用就是因為encoding/json支持了結(jié)構(gòu)體標(biāo)簽,不過他有自己的標(biāo)簽規(guī)則;但是他們都有一個總體規(guī)則,這個規(guī)則是不能更改的,具體格式如下:
`key1:"value1"?key2:"value2"?key3:"value3"...`??//?鍵值對用空格分隔
結(jié)構(gòu)體標(biāo)簽可以有多個鍵值對,鍵與值要用冒號分隔,值要使用雙引號括起來,多個鍵值對之間要使用一個空格分隔,千萬不要使用逗號?。。?/p>
如果我們想要在一個值中傳遞多個信息怎么辦?不同庫中實現(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ī)范的,但是很容易出錯,因為Go語言在編譯階段并不會對其格式做合法鍵值對的檢查,這樣我們不小心寫錯了,就很難被發(fā)現(xiàn),不過我們有go vet工具做檢查,具體使用來看一個例子:
type?User?struct?{
?Name?string?`abc?def?ghk`
?Age?uint16?`123:?232`
}
func?main()??{
}
然后執(zhí)行go 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值語法錯誤。
所以在我們項目中引入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ù)校驗,方便了許多,之前寫了一篇關(guān)于validate的文章:boss: 這小子還不會使用validator庫進(jìn)行數(shù)據(jù)校驗,開了~~~,可以關(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???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,也可以自己動手實現(xiàn)一個校驗庫,當(dāng)作練手項目。
文中代碼已上傳github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/struct_tag_demo
好啦,本文到這里就結(jié)束了,我是asong,我們下期見。創(chuàng)建了讀者交流群,歡迎各位大佬們踴躍入群,一起學(xué)習(xí)交流。入群方式:關(guān)注公眾號獲取。更多學(xué)習(xí)資料請到公眾號領(lǐng)取。
