前言
哈嘍,大家好,我是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/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這幾個庫的結構體標簽是很常用的,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/Golang_Dream/tree/master/code_demo/struct_tag_demo
原文鏈接:https://mp.weixin.qq.com/s/3sz8oE8nGmba8WECXa0AWg