37. 反引號的妙用:結構體里的 Tag 標簽
點擊上方“Go編程時光”,選擇“加為星標”
第一時間關注Go技術干貨!

1. 拋磚引玉:什么是 Tag?
正常情況下,你定義的結構體是這樣子的,每個字段都由名字和字段類型組成
type?Person?struct?{
????Name?string?
????Age??int???
????Addr?string
}
也有例外,就像下面這樣子,字段上還可以額外再加一個屬性,用反引號(Esc鍵下面的那個鍵)包含的字符串,稱之為 Tag,也就是標簽。
type?Person?struct?{
????Name?string?`json:"name"`
????Age??int????`json:"age"`
????Addr?string?`json:"addr,omitempty"`
}
那么這個標簽有什么用呢?
這邊先以 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,就不會打印了
????fmt.Printf("%s\n",?data1)
????//?================
????p2?:=?Person{
????????Name:?"Jack",
????????Age:??22,
????????Addr:?"China",
????}
????data2,?err?:=?json.Marshal(p2)
????if?err?!=?nil?{
????????panic(err)
????}
????//?p2?則會打印所有
????fmt.Printf("%s\n",?data2)
}
由于 Person 結構體里的 Addr 字段有 omitempty 屬性,因此 encoding/json 在將對象轉化 json 字符串時,只要發(fā)現(xiàn)對象里的 Addr 為 ?false, 0, 空指針,空接口,空數(shù)組,空切片,空映射,空字符串中的一種,就會被忽略。
因此運行后,輸出的結果如下
$?go?run?demo.go?
{"name":"Jack","age":22}
{"name":"Jack","age":22,"addr":"China"}
2. 不懂就問:如何定義獲取 Tag ?
Tag 由反引號包含,由一對或幾對的鍵值對組成,通過空格來分割鍵值。格式如下
`key01:"value01"?key02:"value02"?key03:"value03"`
定義完后,如何從結構體中,取出 Tag 呢?
答案就是,我們上一節(jié)學過的 "反射"。
獲取 Tag 可以分為三個步驟:
獲取字段 field
獲取標簽 tag
獲取鍵值對 key:value
演示如下
//?三種獲取?field
field?:=?reflect.TypeOf(obj).FieldByName("Name")
field?:=?reflect.ValueOf(obj).Type().Field(i)??//?i?表示第幾個字段
field?:=?reflect.ValueOf(&obj).Elem().Type().Field(i)??//?i?表示第幾個字段
//?獲取?Tag
tag?:=?field.Tag?
//?獲取鍵值對
labelValue?:=?tag.Get("label")
labelValue,ok?:=?tag.Lookup("label")
獲取鍵值對,有Get 和 Lookup 兩種方法,但其實 Get 只是對 Lookup 函數(shù)的簡單封裝而已,當沒有獲取到對應 tag 的內容,會返回空字符串。
func?(tag?StructTag)?Get(key?string)?string?{
????v,?_?:=?tag.Lookup(key)
????return?v
}
空 Tag 和不設置 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. 實戰(zhàn)一下:利用 Tag 搞點事情?
學會了如何定義 tag 和 獲取 tag,可以試著利用 ?tag 來做一些事情,來練習一下。
這邊我舉個例子吧。
如果我想實現(xiàn)一個函數(shù)(就叫 Print 吧),在打印 person 對象時,能夠美化輸出
type?Person?struct?{
????Name????????string?
????Age?????????int????
????Gender??????string
}
person?:=?Person{
????Name:????????"MING",
????Age:?????????29,
}
Print(person)
就像下面這樣,key 和 value 之間有個 is:,如果沒有指定 Gender 的值,那么顯示為unknown(未知)。
Name?is:?MING
Age?is:?29
Gender?is:?unknown
那該怎么做呢?
先改造下 Person 結構體,給每個字段加上 tag 標簽,三個字段的tag 都有 label 屬性,而 Gender 多了一個 default 屬性,意在指定默認值。
type?Person?struct?{
????Name????????string?`label:"Name?is:?"`
????Age?????????int????`label:"Age?is:?"`
????Gender??????string?`label:"Gender?is:?"?default:"unknown"`
}
然后來寫一下這個 ?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?==?""?{
????????????//?如果沒有指定值,則用默認值替代
????????????value?=?defaultValue
????????}
????????fmt.Println(label?+?value)
????}
????return?nil
}
最后執(zhí)行一下,看了下輸出,符合我們的預期:
$?go?run?demo.go?
Name?is:?MING
Age?is:?29
Gender?is:?unknown
至此,我們就把 Tag 的用法介紹完了。
明哥原創(chuàng)文都已傳至 Github:https://github.com/iswbm/GolangCodingTime
本文永久博客鏈接:http://golang.iswbm.com/en/latest/c02/c02_10.html
留言糾錯
由于這個號,沒有留言功能,文章中不可避免(盡管我已經盡力驗證充分)會出現(xiàn)一些小毛病。
為了避免文章因我個人語言表達不當,或者個人理解有誤,而誤導初學者,請大家共同監(jiān)督,若文章有說的不對的地方,請在下方鏈接里留言,幫我指正。謝謝大家,共同努力。Fighting

???
