參考
《快學(xué) Go 語言》第 15 課 —— 反射
golang reflect反射(一):interface接口的入門(大白話)
golang reflect反射(二):interface接口的理解
反射是 Go 語言學(xué)習(xí)的一個難點镣典,但也是非常重要的一個知識點。反射是洞悉 Go 語言類型系統(tǒng)設(shè)計的法寶,Go 語言的 ORM 庫離不開它,Go 語言的 json 序列化庫離不開它,Go 語言的運行時更是離不開它烛卧。筆者在學(xué)習(xí)反射功能的時候也是費了好大一番功夫才敢說自己確實搞懂了。下面請讀者跟著我的步伐來一步一步深入理解反射功能。
一绞佩、反射的目標(biāo)
反射的目標(biāo)之一是獲取變量的類型信息,例如這個類型的名稱猪钮、占用字節(jié)數(shù)品山、所有的方法列表、所有的內(nèi)部字段結(jié)構(gòu)烤低、它的底層存儲類型等等肘交。
反射的目標(biāo)之二是動態(tài)的修改變量內(nèi)部字段值。比如 json 的反序列化扑馁,你有的是對象內(nèi)部字段的名稱和相應(yīng)的值涯呻,你需要把這些字段的值循環(huán)填充到對象相應(yīng)的字段里凉驻。
二、元類型reflect.Kind
reflect 包定義了十幾種內(nèi)置的「元類型」复罐,每一種元類型都有一個整數(shù)編號涝登,這個編號使用 reflect.Kind 類型表示。不同的結(jié)構(gòu)體是不同的類型效诅,但是它們都是同一個元類型 Struct胀滚。包含不同子元素的切片也是不同的類型,但是它們都會同一個元類型 Slice乱投。
type Kind uint
const (
Invalid Kind = iota // 不存在的無效類型
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr // 指針的整數(shù)類型咽笼,對指針進(jìn)行整數(shù)運算時使用
Float32
Float64
Complex64
Complex128
Array // 數(shù)組類型
Chan // 通道類型
Func // 函數(shù)類型
Interface // 接口類型
Map // 字典類型
Ptr // 指針類型
Slice // 切片類型
String // 字符串類型
Struct // 結(jié)構(gòu)體類型
UnsafePointer // unsafe.Pointer 類型
)
三、反射的基礎(chǔ)代碼TypeOf() 和 ValueOf()
reflect 包提供了兩個基礎(chǔ)反射方法戚炫,分別是 TypeOf() 和 ValueOf() 方法剑刑,分別用于獲取變量的類型和值,定義如下
func TypeOf(v interface{}) Type
func ValueOf(v interface{}) Value
下面是一個簡單的例子嘹悼,對結(jié)構(gòu)體變量進(jìn)行反射
package main
import "fmt"
import "reflect"
func main() {
var s int = 42
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.ValueOf(s))
}
--------
int
42
這兩個方法的參數(shù)是 interface{} 類型叛甫,意味著調(diào)用時編譯器首先會將目標(biāo)變量轉(zhuǎn)換成 interface{} 類型。在接口小節(jié)我們提到接口類型包含兩個指針杨伙,一個指向類型其监,一個指向值,上面兩個方法的作用就是將接口變量進(jìn)行解剖分離出類型和值限匣。
TypeOf() 方法返回變量的類型信息得到的是一個類型為 reflect.Type 的變量抖苦,ValueOf() 方法返回變量的值信息得到的是一個類型為 reflect.Value 的變量。
四米死、reflect.Type
它是一個接口類型锌历,里面定義了非常多的方法用于獲取和這個類型相關(guān)的一切信息。這個接口的結(jié)構(gòu)體實現(xiàn)隱藏在 reflect 包里峦筒,每一種類型都有一個相關(guān)的類型結(jié)構(gòu)體來表達(dá)它的結(jié)構(gòu)信息究西。
type Type interface {
...
Method(i int) Method // 獲取掛在類型上的第 i'th 個方法
...
NumMethod() int // 該類型上總共掛了幾個方法
Name() string // 類型的名稱
PkgPath() string // 所在包的名稱
Size() uintptr // 占用字節(jié)數(shù)
String() string // 該類型的字符串形式
Kind() Kind // 元類型
...
Bits() // 占用多少位
ChanDir() // 通道的方向
...
Elem() Type // 數(shù)組,切片物喷,通道卤材,指針,字典(key)的內(nèi)部子元素類型
Field(i int) StructField // 獲取結(jié)構(gòu)體的第 i'th 個字段
...
In(i int) Type // 獲取函數(shù)第 i'th 個參數(shù)類型
Key() Type // 字典的 key 類型
Len() int // 數(shù)組的長度
NumIn() int // 函數(shù)的參數(shù)個數(shù)
NumOut() int // 函數(shù)的返回值個數(shù)
Out(i int) Type // 獲取函數(shù) 第 i'th 個返回值類型
common() *rtype // 獲取類型結(jié)構(gòu)體的共同部分
uncommon() *uncommonType // 獲取類型結(jié)構(gòu)體的不同部分
}
所有的類型結(jié)構(gòu)體都包含一個共同的部分信息峦失,這部分信息使用 rtype 結(jié)構(gòu)體描述扇丛,rtype 實現(xiàn)了 Type 接口的所有方法。剩下的不同的部分信息各種特殊類型結(jié)構(gòu)體都不一樣尉辑》可以將 rtype 理解成父類,特殊類型的結(jié)構(gòu)體是子類,會有一些不一樣的字段信息卓练。
// 基礎(chǔ)類型 rtype 實現(xiàn)了 Type 接口
type rtype struct {
size uintptr // 占用字節(jié)數(shù)
ptrdata uintptr
hash uint32 // 類型的hash值
...
kind uint8 // 元類型
...
}
// 切片類型
type sliceType struct {
rtype
elem *rtype // 元素類型
}
// 結(jié)構(gòu)體類型
type structType struct {
rtype
pkgPath name // 所在包名
fields []structField // 字段列表
}
...
五隘蝎、reflect.Value
不同于 reflect.Type 接口,reflect.Value 是結(jié)構(gòu)體類型昆庇,一個非常簡單的結(jié)構(gòu)體末贾。
type Value struct {
typ *rtype // 變量的類型結(jié)構(gòu)體
ptr unsafe.Pointer // 數(shù)據(jù)指針
flag uintptr // 標(biāo)志位
}
這個接口體包含變量的類型結(jié)構(gòu)體指針、數(shù)據(jù)的地址指針和一些標(biāo)志位信息整吆。里面的類型結(jié)構(gòu)體指針字段就是上面的 rtype 結(jié)構(gòu)體地址拱撵,存儲了變量的類型信息。標(biāo)志位里有幾個位存儲了值的「元類型」表蝙。下面我們看個簡單的例子
package main
import "reflect"
import "fmt"
func main() {
type SomeInt int
var s SomeInt = 42
var t = reflect.TypeOf(s)
var v = reflect.ValueOf(s)
// reflect.ValueOf(s).Type() 等價于 reflect.TypeOf(s)
fmt.Println(t == v.Type())
fmt.Println(v.Kind() == reflect.Int) // 元類型
// 將 Value 還原成原來的變量
var is = v.Interface()
fmt.Println(is.(SomeInt))
}
----------
true
true
42
Value 結(jié)構(gòu)體的 Type() 方法也可以返回變量的類型信息拴测,它可以作為 reflect.TypeOf() 函數(shù)的替代品,沒有區(qū)別府蛇。通過 Value 結(jié)構(gòu)體提供的 Interface() 方法可以將 Value 還原成原來的變量值集索。注意這里使用了類型斷言,如果想做+1這種數(shù)字操作汇跨,直接用is這個變量是無法通過的务荆。
將上面的各種關(guān)系整理一下,可以得到下面這張圖
Value 這個結(jié)構(gòu)體雖然很簡單穷遂,但是附著在 Value 上的方法非常之多函匕,主要是用來方便用戶讀寫 ptr 字段指向的數(shù)據(jù)內(nèi)存。雖然我們也可以通過 unsafe 包來精細(xì)操控內(nèi)存蚪黑,但是使用過于繁瑣盅惜,使用 Value 結(jié)構(gòu)體提供的方法會更加簡單直接。
func (v Value) SetLen(n int) // 修改切片的 len 屬性
func (v Value) SetCap(n int) // 修改切片的 cap 屬性
func (v Value) SetMapIndex(key, val Value) // 修改字典 kv
func (v Value) Send(x Value) // 向通道發(fā)送一個值
func (v Value) Recv() (x Value, ok bool) // 從通道接受一個值
// Send 和 Recv 的非阻塞版本
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
// 獲取切片忌穿、字符串抒寂、數(shù)組的具體位置的值進(jìn)行讀寫
func (v Value) Index(i int) Value
// 根據(jù)名稱獲取結(jié)構(gòu)體的內(nèi)部字段值進(jìn)行讀寫
func (v Value) FieldByName(name string) Value
// 將接口變量裝成數(shù)組,一個是類型指針掠剑,一個是數(shù)據(jù)指針
func (v Value) InterfaceData() [2]uintptr
// 根據(jù)名稱獲取結(jié)構(gòu)體的方法進(jìn)行調(diào)用
// Value 結(jié)構(gòu)體的數(shù)據(jù)指針 ptr 可以指向方法體
func (v Value) MethodByName(name string) Value
...
值得注意的是屈芜,觀察 Value 結(jié)構(gòu)體提供的很多方法,其中有不少會返回 Value 類型朴译。比如反射數(shù)組類型的 Index(i int) 方法沸伏,它會返回一個新的 Value 對象,這個對象的類型指向數(shù)組內(nèi)部子元素的類型动分,對象的數(shù)據(jù)指針會指向數(shù)組指定位置子元素所在的內(nèi)存。
六红选、理解 Go 語言官方的反射三大定律
官方對 Go 語言的反射功能做了一個抽象的描述澜公,總結(jié)出了三大定律,分別是
1.Reflection goes from interface value to reflection object.
2.Reflection goes from reflection object to interface value.
3.To modify a reflection object, the value must be settable.
1.第一個定律的意思是反射將接口變量轉(zhuǎn)換成反射對象 Type 和 Value,這個很好理解坟乾,就是下面這兩個方法的功能
func TypeOf(v interface{}) Type
func ValueOf(v interface{}) Value
2.第二個定律的意思是反射可以通過反射對象 Value 還原成原先的接口變量迹辐,這個指的就是 Value 結(jié)構(gòu)體提供的 Interface() 方法。注意它得到的是一個接口變量甚侣,如果要換成成原先的變量還需要經(jīng)過一次造型明吩。
func (v Value) Interface() interface{}
3.前兩個定律比較簡單,它的意思可以使用前面畫的反射關(guān)系圖來表達(dá)殷费。第三個定律的功能不是很好理解印荔,它的意思是想用反射功能來修改一個變量的值,前提是這個值可以被修改详羡。
值類型的變量是不可以通過反射來修改仍律,因為在反射之前,傳參的時候需要將值變量轉(zhuǎn)換成接口變量实柠,值內(nèi)容會被淺拷貝水泉,反射對象 Value 指向的數(shù)據(jù)內(nèi)存地址不是原變量的內(nèi)存地址,而是拷貝后的內(nèi)存地址窒盐。這意味著如果值類型變量可以通過反射功能來修改草则,那么修改操作根本不會影響到原變量的值,那就白白修改了蟹漓。所以 reflect 包就直接禁止了通過反射來修改值類型的變量炕横。我們看個例子
package main
import "reflect"
func main() {
var s int = 42
var v = reflect.ValueOf(s)
v.SetInt(43)
}
---------
panic: reflect: reflect.Value.SetInt using unaddressable value
goroutine 1 [running]:
reflect.flag.mustBeAssignable(0x82)
/usr/local/go/src/reflect/value.go:234 +0x157
reflect.Value.SetInt(0x107a1a0, 0xc000016098, 0x82, 0x2b)
/usr/local/go/src/reflect/value.go:1472 +0x2f
main.main()
/Users/qianwp/go/src/github.com/pyloque/practice/main.go:8 +0xc0
exit status 2
嘗試通過反射來修改整型變量失敗了,程序直接拋出了異常牧牢。下面我們來嘗試通過反射來修改指針變量指向的值看锉,這個是可行的。
package main
import "fmt"
import "reflect"
func main() {
var s int = 42
// 反射指針類型
var v = reflect.ValueOf(&s)
// 要拿出指針指向的元素進(jìn)行修改
v.Elem().SetInt(43)
fmt.Println(s)
}
-------
43
可以看到變量 s 的值確實被修改成功了塔鳍,不過這個例子修改的是指針指向的值而不是修改指針變量本身伯铣,如果不使用 Elem() 方法進(jìn)行修改也會拋出一樣的異常。
結(jié)構(gòu)體也是值類型轮纫,也必須通過指針類型來修改腔寡。下面我們嘗試使用反射來動態(tài)修改結(jié)構(gòu)體內(nèi)部字段的值。
package main
import "fmt"
import "reflect"
type Rect struct {
Width int
Height int
}
func SetRectAttr(r *Rect, name string, value int) {
var v = reflect.ValueOf(r)
var field = v.Elem().FieldByName(name)
field.SetInt(int64(value))
}
func main() {
var r = Rect{50, 100}
SetRectAttr(&r, "Width", 100)
SetRectAttr(&r, "Height", 200)
fmt.Println(r)
}
-----
{100 200}
七掌唾、內(nèi)置庫反射的例子
節(jié)選自《Go語言圣經(jīng)P427》
一個大家熟悉的例子是fmt.Fprintf函數(shù)提供的字符串格式化處理邏輯放前,它可以用例對任意類型的值格式化并打印,甚至支持用戶自定義的類型糯彬。讓我們也來嘗試實現(xiàn)一個類似功能的函數(shù)凭语。為了簡單起見,我們的函數(shù)只接收一個參數(shù)撩扒,然后返回和fmt.Sprint類似的格式化后的字符串似扔。我們實現(xiàn)的函數(shù)名也叫Sprint。
我們使用了switch類型分支首先來測試輸入?yún)?shù)是否實現(xiàn)了String方法,如果是的話就使用該方法炒辉。然后繼續(xù)增加類型測試分支豪墅,檢查是否是每個基于string、int黔寇、bool等基礎(chǔ)類型的動態(tài)類型偶器,并在每種情況下執(zhí)行相應(yīng)的格式化操作。
func Sprint(x interface{}) string {
type stringer interface {
String() string
}
switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
// ...similar cases for int16, uint32, and so on...
case bool:
if x {
return "true"
}
return "false"
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}
但是我們?nèi)绾翁幚砥渌愃芠]float64缝裤、map[string][]string等類型呢屏轰?我們當(dāng)然可以添加更多的測試分支,但是這些組合類型的數(shù)目基本是無窮的倘是。還有如何處理url.Values等命名的類型呢亭枷?雖然類型分支可以識別出底層的基礎(chǔ)類型是map[string][]string,但是它并不匹配url.Values類型搀崭,因為它們是兩種不同的類型叨粘,而且switch類型分支也不可能包含每個類似url.Values的類型,這會導(dǎo)致對這些庫的循環(huán)依賴瘤睹。沒有一種方法來檢查未知類型的表示方式升敲,我們被卡住了。這就是我們?yōu)楹涡枰瓷涞脑颉?/p>
現(xiàn)在使用 reflect.Value 的 Kind 方法來替代之前的類型 switch. 雖然還是有無窮多的類型, 但是它們的kinds類型卻是有限的轰传。
package format
import (
"reflect"
"strconv"
)
// Any formats any value as a string.
func Any(value interface{}) string {
return formatAtom(reflect.ValueOf(value))
}
// formatAtom formats a value without inspecting its internal structure.
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
// ...floating-point and complex cases omitted for brevity...
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default: // reflect.Array, reflect.Struct, reflect.Interface
return v.Type().String() + " value"
}
}
到目前為止, 我們的函數(shù)將每個值視作一個不可分割沒有內(nèi)部結(jié)構(gòu)的, 因此它叫 formatAtom.對于聚合類型(結(jié)構(gòu)體和數(shù)組)個接口只是打印類型的值, 對于引用類型(channels, functions,pointers, slices, 和 maps), 它十六進(jìn)制打印類型的引用地址. 雖然還不夠理想, 但是依然是一個重大的進(jìn)步, 并且 Kind 只關(guān)心底層表示, format.Any 也支持新命名的類型. 例如:
var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x)) // "1"
fmt.Println(format.Any(d)) // "1"
// "[]int64 0x8202b87b0"
fmt.Println(format.Any([]int64{x}))
// "[]time.Duration 0x8202b87e0"
fmt.Println(format.Any([]time.Duration8kyjy28))
八驴党、Golang的反射reflect深入理解和示例
1.未知原有類型【遍歷探測其Filed】
很多情況下,我們可能并不知道其具體類型获茬,那么這個時候港庄,該如何做呢?需要我們進(jìn)行遍歷探測其Filed來得知恕曲,示例如下:
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) ReflectCallFunc() {
fmt.Println("Allen.Wu ReflectCallFunc")
}
func main() {
user := User{1, "Allen.Wu", 25}
DoFiledAndMethod(user)
}
// 通過接口來獲取任意參數(shù)鹏氧,然后一一揭曉
func DoFiledAndMethod(input interface{}) {
getType := reflect.TypeOf(input)
fmt.Println("get Type is :", getType.Name())
getValue := reflect.ValueOf(input)
fmt.Println("get all Fields is:", getValue)
// 獲取方法字段
// 1. 先獲取interface的reflect.Type,然后通過NumField進(jìn)行遍歷
// 2. 再通過reflect.Type的Field獲取其Field
// 3. 最后通過Field的Interface()得到對應(yīng)的value
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
// 獲取方法
// 1. 先獲取interface的reflect.Type佩谣,然后通過.NumMethod進(jìn)行遍歷
for i := 0; i < getType.NumMethod(); i++ {
m := getType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
運行結(jié)果:
get Type is : User
get all Fields is: {1 Allen.Wu 25}
Id: int = 1
Name: string = Allen.Wu
Age: int = 25
ReflectCallFunc: func(main.User)
這里注意把还,getType和getValue返回的Field是不同的,另外Elem也不同:
getType.Field(i int) StructField
getValue.Field(i int) Value
getType. Elem() Type
getValue:func (v Value) Elem() Value {
type StructField struct {
Name string // name 常用
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
}
2.通過reflect.Value設(shè)置實際變量的值
reflect.Value是通過reflect.ValueOf(X)獲得的茸俭,只有當(dāng)X是指針的時候吊履,才可以通過reflec.Value修改實際變量X的值,即:要修改反射類型的對象就一定要保證其值是“addressable”的调鬓。
示例如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
fmt.Println("old value of pointer:", num)
// 通過reflect.ValueOf獲取num中的reflect.Value艇炎,
//注意,參數(shù)必須是指針才能修改其值
pointer := reflect.ValueOf(&num)
newValue := pointer.Elem()
fmt.Println("type of pointer:", newValue.Type())
fmt.Println("settability of pointer:", newValue.CanSet())
// 重新賦值
newValue.SetFloat(77)
fmt.Println("new value of pointer:", num)
////////////////////
// 如果reflect.ValueOf的參數(shù)不是指針腾窝,會如何冕臭?
pointer = reflect.ValueOf(num)
// 如果非指針腺晾,這里直接panic,“panic: reflect:
//call of reflect.Value.Elem on float64 Value”
//newValue = pointer.Elem()
}
運行結(jié)果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value of pointer: 77
3.通過reflect.ValueOf來進(jìn)行方法的調(diào)用
這算是一個高級用法了辜贵,前面我們只說到對類型、變量的幾種反射的用法归形,包括如何獲取其值托慨、其類型、如果重新設(shè)置新值暇榴。但是在工程應(yīng)用中厚棵,另外一個常用并且屬于高級的用法,就是通過reflect來進(jìn)行方法【函數(shù)】的調(diào)用蔼紧。比如我們要做框架工程的時候婆硬,需要可以隨意擴展方法,或者說用戶可以自定義方法奸例,那么我們通過什么手段來擴展讓用戶能夠自定義呢彬犯?關(guān)鍵點在于用戶的自定義方法是未可知的,因此我們可以通過reflect來搞定
示例如下:
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) ReflectCallFuncHasArgs(name string, age int) {
fmt.Println("ReflectCallFuncHasArgs name: ", name,
", age:", age, "and origal User.Name:", u.Name)
}
func (u User) ReflectCallFuncNoArgs() {
fmt.Println("ReflectCallFuncNoArgs")
}
// 如何通過反射來進(jìn)行方法的調(diào)用查吊?
// 本來可以用u.ReflectCallFuncXXX直接調(diào)用的谐区,但是如果要通過反射,
//那么首先要將方法注冊逻卖,也就是MethodByName宋列,然后通過反射調(diào)動mv.Call
func main() {
user := User{1, "Allen.Wu", 25}
// 1. 要通過反射來調(diào)用起對應(yīng)的方法,必須要先通過
//reflect.ValueOf(interface)來獲取到reflect.Value评也,
//得到“反射類型對象”后才能做下一步處理
getValue := reflect.ValueOf(user)
// 一定要指定參數(shù)為正確的方法名
// 2. 先看看帶有參數(shù)的調(diào)用方法
methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
methodValue.Call(args)
// 一定要指定參數(shù)為正確的方法名
// 3. 再看看無參數(shù)的調(diào)用方法
methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
args = make([]reflect.Value, 0)
methodValue.Call(args)
}
運行結(jié)果:
ReflectCallFuncHasArgs name: wudebao , age: 30 and origal User.Name: Allen.Wu
ReflectCallFuncNoArgs
九炼杖、golang中struct關(guān)于反射tag
關(guān)于結(jié)構(gòu)體中的json Tag,可以參考Golang json盗迟。其實除了json Tag坤邪,結(jié)構(gòu)體也支持自定義Tag,然后自己利用反射來解析,可以參考golang自定義struct字段標(biāo)簽
package main
import (
"fmt"
"reflect"
)
type User struct {
name string `json:name-field`
age int
}
func main() {
user := &User{"John Doe The Fourth", 20}
field, ok := reflect.TypeOf(user).Elem().FieldByName("name")
if !ok {
panic("Field not found")
}
fmt.Println(getStructTag(field))
}
func getStructTag(f reflect.StructField) string {
return string(f.Tag)
}
output:
json:name-field
由反射可直接得到結(jié)構(gòu)域诈乒,調(diào)用結(jié)構(gòu)域中的Tag即可獲取到tag進(jìn)行處理罩扇。在golang中,例如著名的xorm包都沒有加入mongodb的支持怕磨。利用golang的struct很好的支持JSON還是比較困難的喂饥,大部分都得依賴tag。在底層使用反射獲得tag內(nèi)容還是非常有必要的肠鲫。
看一下源碼:
// A StructTag is the tag string in a struct field.
//
// By convention, tag strings are a concatenation of
// optionally space-separated key:"value" pairs.
// Each key is a non-empty string consisting of non-control
// characters other than space (U+0020 ' '), quote (U+0022 '"'),
// and colon (U+003A ':'). Each value is quoted using U+0022 '"'
// characters and Go string literal syntax.
type StructTag string
// Get returns the value associated with key in the tag string.
// If there is no such key in the tag, Get returns the empty string.
// If the tag does not have the conventional format, the value
// returned by Get is unspecified. To determine whether a tag is
// explicitly set to the empty string, use Lookup.
func (tag StructTag) Get(key string) string {
v, _ := tag.Lookup(key)
return v
}
// Lookup returns the value associated with key in the tag string.
// If the key is present in the tag the value (which may be empty)
// is returned. Otherwise the returned value will be the empty string.
// The ok return value reports whether the value was explicitly set in
// the tag string. If the tag does not have the conventional format,
// the value returned by Lookup is unspecified.
func (tag StructTag) Lookup(key string) (value string, ok bool) {
...
使用Get方法:
package main
import (
"fmt"
"reflect"
)
type Config struct {
Listen string `json:"listen" toml:"listen"`
UserAPIURL string `json:"userapisrv" toml:"userAPIURL"`
SecretKey string `json:"secret_key" toml:"secret_key"`
AuthorityUrl string `json:"authorityapisrv" toml:"authority_url"`
ResourceKey string `json:"resource_key" toml:"resource_key"`
}
func main() {
c := &Config{}
cType := reflect.TypeOf(c)
cValue := reflect.ValueOf(c)
structLen := cValue.Elem().NumField()
structMap := make(map[string]string, structLen)
for i := 0; i < structLen; i++ {
field := cType.Elem().Field(i)
jsonName := field.Tag.Get("json")
fmt.Println(fmt.Sprintf("index:%d,jsonName:%s",i,jsonName))
structMap[jsonName] = field.Name
}
}
------------------
index:0,jsonName:listen
index:1,jsonName:userapisrv
index:2,jsonName:secret_key
index:3,jsonName:authorityapisrv
index:4,jsonName:resource_key