盤點一下結(jié)構(gòu)體標簽在Go中的應(yīng)用
掌握了Go語言的朋友們應(yīng)該都知道,在Go的結(jié)構(gòu)體類型聲明里面,字段聲明后可以跟一個可選的字符串標簽。
type?User?struct?{
????Name?string?`json:"name"`
}
上面是一個標準的例子,Name字段聲明中指定了標簽json:"name" xml:"name" ,這個標簽值看著有點類似Java程序里給類屬性加的注解。
那么這些結(jié)構(gòu)體標簽有什么用途呢,我們隨便寫管用嗎?我們平時工作中常用的結(jié)構(gòu)體標簽有哪些呢?我們能不能自己定義結(jié)構(gòu)體標簽?今天就帶大家掰扯清楚這些問題!
結(jié)構(gòu)體標簽
Go語言允許我們通過結(jié)構(gòu)體字段標簽給一個字段附加可以被反射獲取的”元信息“,正好我們上篇文章實戰(zhàn)演示Go反射的使用方法和應(yīng)用場景中講了Go語言反射使用方法相關(guān)的內(nèi)容,對反射不清楚的可以先去再復(fù)習一下。
通常情況下,結(jié)構(gòu)體標簽被用于提供結(jié)構(gòu)體字段如何被編碼為或者解碼自另外一種格式的轉(zhuǎn)換信息(或者是以何種形式被保存至/獲取自數(shù)據(jù)庫)。不過,你也可以用它存儲任何你想要設(shè)置的”元信息“,供其他包或者自己使用。
使用規(guī)范
結(jié)構(gòu)體標簽在使用上通常是遵守下面三個規(guī)范。
結(jié)構(gòu)體標簽字符串的值是一個由空格分隔的 key:"value" 對列表,例如:
type?User?struct?{
????Name?string?`json:"name"?xml:"name"`
}
鍵,通常表示后面跟的“值”是被哪個包使用的,例如json這個鍵會被encoding/json包處理使用。如果要在“鍵”對應(yīng)的“值”中傳遞多個信息,通常通過用逗號(',')分隔來指定,例如
Name?string?`json:"name,omitempty"`
按照慣例,如果一個字段的結(jié)構(gòu)體標簽里某個鍵的“值”被設(shè)置成了的破折號 ('-'),那么就意味著告訴處理該結(jié)構(gòu)體標簽鍵值的進程排除該字段。例如,把一個字段的標簽設(shè)置成下面這樣
Name?string?`json:"-"`
就以為進行JSON編碼/解碼時忽略Name這個字段。
怎么獲取到結(jié)構(gòu)體標簽
從一開始我們就說結(jié)構(gòu)體標簽是給反射準備的,那么怎么在Go程序里用反射獲得到字段的結(jié)構(gòu)體標簽?zāi)兀靠戳宋覀兩弦黄恼碌耐瑢W,應(yīng)該會知道,結(jié)構(gòu)體字段類型相關(guān)的信息,在反射的世界里使用reflect.StructFiled這個類型表示的。
type?StructField?struct?{
?Name?string
?Type??????Type??????//?field?type
?Tag???????StructTag?//?field?tag?string
??......
}
如上所示,其中包含的Tag字段即代表了字段聲明中的結(jié)構(gòu)體標簽信息。讓我們通過自定義結(jié)構(gòu)體標簽的例子來演示一下怎么使用它在反射里讀取到標簽里的信息。
用反射獲取到自定義的結(jié)構(gòu)體標簽
使用反射reflect包訪問結(jié)構(gòu)體字段的標簽值,我們需要先獲取到結(jié)構(gòu)體的類型信息Type,然后使用Type.Field(i int) 或 Type.FieldByName(name string),方法查詢字段信息,這兩個方法都會返回一個StructField類型的值,上面我們也說了它在反射的世界里用于描述一個結(jié)構(gòu)體字段;而StructField.Tag 是一個StructTag 類型的值,它描述了字段的標簽。
上面我們談到了結(jié)構(gòu)體標簽的使用規(guī)范,如果遵循規(guī)范給字段設(shè)置了標簽后,就可以使用StructTag的Get方法解析標簽的值并返回你指定的鍵的“值”。
func?(tag?StructTag)?Get(key?string)?string
為了方便判斷一個給定的key是否存在與標簽中,StructTag還提供了一個Lookup方法
func?(tag?StructTag)?Lookup(key?string)?(value?string,?ok?bool)
跟Get方法不同的是,Lookup會通過返回的ok值告知給定key是否存在與標簽中。
下面通過一個例子,演示下獲取我們自定義標簽的過程。
package?main
import?(
?"fmt"
?"reflect"
)
type?User?struct?{
?Name??string?`mytag:"MyName"`
?Email?string?`mytag:"MyEmail"`
}
func?main()?{
?u?:=?User{"Bob",?"[email protected]"}
?t?:=?reflect.TypeOf(u)
?for?i?:=?0;?i???field?:=?t.Field(i)
??fmt.Printf("Field:?User.%s\n",?field.Name)
??fmt.Printf("\tWhole?tag?value?:?%s\n",?field.Tag)
??fmt.Printf("\tValue?of?'mytag':?%s\n",?field.Tag.Get("mytag"))
?}
}
上面的程序會輸出
Field:?User.Name
????????Whole?tag?value?:?mytag:"MyName"
????????Value?of?'mytag':?MyName
Field:?User.Email
????????Whole?tag?value?:?mytag:"MyEmail"
????????Value?of?'mytag':?MyEmail
常用的結(jié)構(gòu)體標簽鍵
常用的結(jié)構(gòu)體標簽Key,指的是那些被一些常用的開源包聲明使用的結(jié)構(gòu)體標簽鍵。在這里總結(jié)了一些,都是一些我們平時會用到的包,它們是:
json: ?由encoding/json包使用,詳見json.Marshal()的使用方法和實現(xiàn)邏輯。xml: ?由encoding/xml包使用,詳見xml.Marshal()。bson: ?由gobson包,和mongo-go包使用。protobuf: ?由github.com/golang/protobuf/proto使用,在包文檔中有詳細說明。yaml: ?由gopkg.in/yaml.v2包使用,詳見yaml.Marshal()。gorm: ?由gorm.io/gorm包使用,示例可以在GORM的文檔中找到。
當然這里列的就是最常用的幾個庫他們提供給我們使用的結(jié)構(gòu)體標簽,歡迎大伙踴躍留言,補充一些自己平時用過的庫提供給開發(fā)者使用的結(jié)構(gòu)體標簽。
總結(jié)
這篇文章算是我們上一篇講Go反射的一個實踐方向的延伸介紹,如果你也想在自己的包里提供一些結(jié)構(gòu)體標簽鍵,讓自己的包更易用些,除了看咱們這篇文章外,還可以去看看上面咱們介紹的幾個類庫,看它們的源碼里是怎么應(yīng)用的,現(xiàn)學現(xiàn)用!
? ?

???
