2020重學(xué)Go系列:37. 反引號(hào)的妙用—結(jié)構(gòu)體里的 Tag 標(biāo)簽

1. 拋磚引玉:什么是 Tag?
正常情況下,你定義的結(jié)構(gòu)體是這樣子的,每個(gè)字段都由名字和字段類型組成
type?Person?struct?{
????Name?string?
????Age??int???
????Addr?string
}
也有例外,就像下面這樣子,字段上還可以額外再加一個(gè)屬性,用反引號(hào)(Esc鍵下面的那個(gè)鍵)包含的字符串,稱之為 Tag,也就是標(biāo)簽。
type?Person?struct?{
????Name?string?`json:"name"`
????Age??int????`json:"age"`
????Addr?string?`json:"addr,omitempty"`
}
那么這個(gè)標(biāo)簽有什么用呢?
這邊先以 encoding/json 庫的用法拋磚引玉,看一下它能起到什么樣的效果。
package?main
import?(
????"encoding/json"
????"fmt"
)
type?Person?struct?{
????Name?string?`json:"name"`
????Age??int????`json:"age"`
????Addr?string?`json:"addr,omitempty"`
}
func?main()?{
????p1?:=?Person{
????????Name:?"Jack",
????????Age:??22,
????}
????data1,?err?:=?json.Marshal(p1)
????if?err?!=?nil?{
????????panic(err)
????}
????//?p1?沒有?Addr,就不會(huì)打印了
????fmt.Printf("%s\n",?data1)
????//?================
????p2?:=?Person{
????????Name:?"Jack",
????????Age:??22,
????????Addr:?"China",
????}
????data2,?err?:=?json.Marshal(p2)
????if?err?!=?nil?{
????????panic(err)
????}
????//?p2?則會(huì)打印所有
????fmt.Printf("%s\n",?data2)
}
由于 Person 結(jié)構(gòu)體里的 Addr 字段有 omitempty 屬性,因此 encoding/json 在將對(duì)象轉(zhuǎn)化 json 字符串時(shí),只要發(fā)現(xiàn)對(duì)象里的 Addr 為 ?false, 0, 空指針,空接口,空數(shù)組,空切片,空映射,空字符串中的一種,就會(huì)被忽略。
因此運(yùn)行后,輸出的結(jié)果如下
$?go?run?demo.go?
{"name":"Jack","age":22}
{"name":"Jack","age":22,"addr":"China"}
2. 不懂就問:如何定義獲取 Tag ?
Tag 由反引號(hào)包含,由一對(duì)或幾對(duì)的鍵值對(duì)組成,通過空格來分割鍵值。格式如下
`key01:"value01"?key02:"value02"?key03:"value03"`
定義完后,如何從結(jié)構(gòu)體中,取出 Tag 呢?
答案就是,我們上一節(jié)學(xué)過的 "反射"。
獲取 Tag 可以分為三個(gè)步驟:
獲取字段 field
獲取標(biāo)簽 tag
獲取鍵值對(duì) key:value
演示如下
//?三種獲取?field
field?:=?reflect.TypeOf(obj).FieldByName("Name")
field?:=?reflect.ValueOf(obj).Type().Field(i)??//?i?表示第幾個(gè)字段
field?:=?reflect.ValueOf(&obj).Elem().Type().Field(i)??//?i?表示第幾個(gè)字段
//?獲取?Tag
tag?:=?field.Tag?
//?獲取鍵值對(duì)
labelValue?:=?tag.Get("label")
labelValue,ok?:=?tag.Lookup("label")
獲取鍵值對(duì),有Get 和 Lookup 兩種方法,但其實(shí) Get 只是對(duì) Lookup 函數(shù)的簡單封裝而已,當(dāng)沒有獲取到對(duì)應(yīng) tag 的內(nèi)容,會(huì)返回空字符串。
func?(tag?StructTag)?Get(key?string)?string?{
????v,?_?:=?tag.Lookup(key)
????return?v
}
空 Tag 和不設(shè)置 Tag 效果是一樣的
package?main
import?(
????"fmt"
????"reflect"
)
type?Person?struct?{
????Name?string?``
????Age?string
}
func?main()?{
????p?:=?reflect.TypeOf(Person{})
????name,?_?:=?p.FieldByName("Name")
????fmt.Printf("%q\n",?name.Tag)?//輸出?""
????age,?_?:=?p.FieldByName("Age")
????fmt.Printf("%q\n",?age.Tag)?//?輸出?""
}
3. 實(shí)戰(zhàn)一下:利用 Tag 搞點(diǎn)事情?
學(xué)會(huì)了如何定義 tag 和 獲取 tag,可以試著利用 ?tag 來做一些事情,來練習(xí)一下。
這邊我舉個(gè)例子吧。
如果我想實(shí)現(xiàn)一個(gè)函數(shù)(就叫 Print 吧),在打印 person 對(duì)象時(shí),能夠美化輸出
type?Person?struct?{
????Name????????string?
????Age?????????int????
????Gender??????string
}
person?:=?Person{
????Name:????????"MING",
????Age:?????????29,
}
Print(person)
就像下面這樣,key 和 value 之間有個(gè) is:,如果沒有指定 Gender 的值,那么顯示為unknown(未知)。
Name?is:?MING
Age?is:?29
Gender?is:?unknown
那該怎么做呢?
先改造下 Person 結(jié)構(gòu)體,給每個(gè)字段加上 tag 標(biāo)簽,三個(gè)字段的tag 都有 label 屬性,而 Gender 多了一個(gè) default 屬性,意在指定默認(rèn)值。
type?Person?struct?{
????Name????????string?`label:"Name?is:?"`
????Age?????????int????`label:"Age?is:?"`
????Gender??????string?`label:"Gender?is:?"?default:"unknown"`
}
然后來寫一下這個(gè) ?Print 函數(shù)
func?Print(obj?interface{})?error?{
????//?取?Value
????v?:=?reflect.ValueOf(obj)
????//?解析字段
????for?i?:=?0;?i?
????????//?取tag
????????field?:=?v.Type().Field(i)
????????tag?:=?field.Tag
????????//?解析label?和?default
????????label?:=?tag.Get("label")
????????defaultValue?:=?tag.Get("default")
????????value?:=?fmt.Sprintf("%v",?v.Field(i))
????????if?value?==?""?{
????????????//?如果沒有指定值,則用默認(rèn)值替代
????????????value?=?defaultValue
????????}
????????fmt.Println(label?+?value)
????}
????return?nil
}
最后執(zhí)行一下,看了下輸出,符合我們的預(yù)期:
$?go?run?demo.go?
Name?is:?MING
Age?is:?29
Gender?is:?unknown
至此,我們就把 Tag 的用法介紹完了。
推薦閱讀
站長 polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛好者值得關(guān)注
