Golang 學(xué)習(xí)筆記十四 反射

參考
《快學(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)系整理一下,可以得到下面這張圖


image.png

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末员帮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子导饲,更是在濱河造成了極大的恐慌捞高,老刑警劉巖氯材,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異硝岗,居然都是意外死亡氢哮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門型檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冗尤,“玉大人,你說我怎么就攤上這事胀溺×哑撸” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵仓坞,是天一觀的道長背零。 經(jīng)常有香客問我,道長无埃,這世上最難降的妖魔是什么徙瓶? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮录语,結(jié)果婚禮上倍啥,老公的妹妹穿的比我還像新娘。我一直安慰自己澎埠,他們只是感情好虽缕,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蒲稳,像睡著了一般氮趋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上江耀,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天剩胁,我揣著相機與錄音,去河邊找鬼祥国。 笑死昵观,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舌稀。 我是一名探鬼主播啊犬,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壁查!你這毒婦竟也來了觉至?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤睡腿,失蹤者是張志新(化名)和其女友劉穎语御,沒想到半個月后峻贮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡应闯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年纤控,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孽锥。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡嚼黔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惜辑,到底是詐尸還是另有隱情,我是刑警寧澤疫赎,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布盛撑,位于F島的核電站,受9級特大地震影響捧搞,放射性物質(zhì)發(fā)生泄漏抵卫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一胎撇、第九天 我趴在偏房一處隱蔽的房頂上張望介粘。 院中可真熱鬧,春花似錦晚树、人聲如沸姻采。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慨亲。三九已至,卻和暖如春宝鼓,著一層夾襖步出監(jiān)牢的瞬間刑棵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工愚铡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛉签,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓沥寥,卻偏偏與公主長得像碍舍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子营曼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 周末的早上對家長和孩子來說是非常幸福的乒验,可以睡睡懶覺了〉仝澹可是就這么個美好的早上锻全,我給白白的浪費掉了狂塘,平日起床的鬧鈴...
    shmily123034閱讀 188評論 0 0
  • 198) 牢記80/20法則,并知道20這部分的要點鳄厌。 199) 區(qū)分重要和不重要的事情荞胡,優(yōu)先處理重要的事情。 1...
    財才閱讀 198評論 0 0
  • 親愛噠: 當(dāng)你能看到特地這封信的時候了嚎,估計是被我認(rèn)準(zhǔn)了以後會一輩子呆在一起的人了泪漂。 今天是2017.11.11。一...
    青晚兮兮閱讀 127評論 0 1
  • 今日大盤在昨日晚間重要消息的影響下,低開低走呐伞,直接往前期低點直去敌卓,雖然在盤中有一定的反彈,但銀行伶氢、地產(chǎn)等權(quán)重藍(lán)籌相...
    王也也王閱讀 244評論 0 0