Go語言中結構體打Tag是什麼意思?

asong發表於2021-11-23

原文連結:Go語言中結構體打Tag是什麼意思?

前言

哈嘍,大家好,我是asong。今天想與大家分享Go語言中結構體標籤是怎麼使用的,以及怎樣定製自己的結構體標籤解析。

大多數初學者在看公司的專案程式碼時,看到的一些結構體定義會是這樣的:

type Location struct {
    Longitude float32 `json:"lon,omitempty"`
    Latitude  float32 `json:"lat,omitempty"`
}

欄位後面會有一個標籤,這個標籤有什麼用呢?

上面的例子中,標籤json:"lon,omitempty"代表的意思是結構體欄位的值編碼為json物件時,每一個匯出欄位變成該物件的一個成員,這個成員的名字為lon或者lat,並且當欄位是空值時,不匯出該欄位;總結就是lonlat是重新命名成員的名字,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 taghttps://github.com/golang/go/...

TagDocumentation
xmlhttps://godoc.org/encoding/xml
jsonhttps://godoc.org/encoding/json
asn1https://godoc.org/encoding/asn1
reformhttps://godoc.org/gopkg.in/re...
dynamodbhttps://docs.aws.amazon.com/s...
bigqueryhttps://godoc.org/cloud.googl...
datastorehttps://godoc.org/cloud.googl...
spannerhttps://godoc.org/cloud.googl...
bsonhttps://godoc.org/labix.org/v..., https://godoc.org/go.mongodb....
gormhttps://godoc.org/github.com/...
yamlhttps://godoc.org/gopkg.in/ya...
tomlhttps://godoc.org/github.com/...
validatehttps://github.com/go-playgro...
mapstructurehttps://godoc.org/github.com/...
parserhttps://godoc.org/github.com/...
protobufhttps://github.com/golang/pro...
dbhttps://github.com/jmoiron/sqlx
urlhttps://github.com/google/go-...
featurehttps://github.com/nikolaydub...

jsonyamlgormvalidatemapstructureprotobuf這幾個庫的結構體標籤是很常用的,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是一個內建型別,提供了GetLoopup兩種方法來解析標籤中的值並返回指定鍵的值:

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,也可以自己動手實現一個校驗庫,當作練手專案。

文中程式碼已上傳githubhttps://github.com/asong2020/...

好啦,本文到這裡就結束了,我是asong,我們下期見。

歡迎關注公眾號:Golang夢工廠

相關文章