• Go学习值reflect反射篇


    零、参考

    一、反射

    1. 介绍

    简言之:就是利用【反射功能】注册【一些信息】到程序可执行文件中,因此在程序运行期间,可以利用【反射功能】取出或修改【这些信息】

    反射是**【指在程序运行期间对程序本身进行访问和修改的能力】**。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

    支持反射的语言可以在程序编译期间将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获取类型的反射信息,并且有能力修改它们。

    2. 为什么要用反射

    常用场景

    需要反射的 2 个常见场景:

    1. 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
    2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
    不推荐使用的理由

    在讲反射的原理以及如何用之前,还是说几点不使用反射的理由:

    1. 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
    2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
    3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

    3. 反射是如何实现的

    当向【接口变量】赋予一个【实体类型】的时候,【接口会存储实体的类型信息】,反射就是【通过接口的类型信息实现的】,反射建立在类型的基础上。

    Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

    • 下面例子是,通过反射获取传入数据的类型
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func reflectType(x interface{}) {
    	v := reflect.TypeOf(x)
    	fmt.Printf("type:%v\n", v)
    }
    func main() {
    	var a float32 = 3.14
    	reflectType(a) // type:float32
    	var b int64 = 100
    	reflectType(b) // type:int64
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    二、反射与接口的关系

    1. interface 的底层结构

    反射主要与 interface{} 类型相关。下面介绍一下 interface 的底层结构

    type iface struct {
        tab  *itab
        data unsafe.Pointer
    }
    
    type itab struct {
        inter  *interfacetype
        _type  *_type
        link   *itab
        hash   uint32
        bad    bool
        inhash bool
        unused [2]byte
        fun    [1]uintptr
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    其中 itab 由具体类型 _type 以及 interfacetype 组成。_type 表示具体类型,而 interfacetype 则表示具体类型实现的接口类型

    实际上,iface 描述的是非空接口,它包含方法;与之相对的是 eface,描述的是空接口,不包含任何方法,Go 语言里有的类型都 “实现了” 空接口。

    type eface struct {
        _type *_type
        data  unsafe.Pointer
    }
    
    • 1
    • 2
    • 3
    • 4

    相比 ifaceeface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值。

    2. interface结构与反射的关系

    img
    1. ValueOf 函数返回值 reflect.Type 主要提供关于类型相关的信息,所以它和 _type 关联比较紧密;

    2. ValueOf 函数返回值 reflect.Value 表示 interface{} 里存储的实际变量,通过 Type() 方法和 Interface() 方法可以打通 interfaceTypeValue 三者。

      • Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。

      • Interface() 方法可以将 Value 还原成原来的 interface。

    reflect 包里定义了一个接口和一个结构体,即 reflect.Typereflect.Value,它们提供很多函数来获取存储在接口里的类型信息。

    • reflect.Type 主要提供关于类型相关的信息,所以它和 _type 关联比较紧密;
    • reflect.Value 则结合 _typedata 两者,因此程序员可以获取甚至改变类型的值。

    reflect 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:

    func TypeOf(i interface{}) Type 
    func ValueOf(i interface{}) Value
    
    • 1
    • 2
    2.1 TypeOf

    TypeOf 函数用来提取一个接口中值的类型信息。由于它的输入参数是一个空的 interface{},调用此函数时,实参会先被转化为 interface{} 类型。这样,实参的类型信息、方法集、值信息都存储到 interface{} 变量里了。

    看下源码:

    // 这里的 `emptyInterface` 和上面提到的 `eface` 是一回事(字段名略有差异,字段是相同的),
    // 且在不同的源码包:前者在 `reflect` 包,后者在 `runtime` 包。 `eface.typ` 就是动态类型。
    func TypeOf(i interface{}) Type {
        eface := *(*emptyInterface)(unsafe.Pointer(&i))
        return toType(eface.typ)
    }
    
    type emptyInterface struct {
        typ  *rtype
        word unsafe.Pointer
    }
    
    // 至于 `toType` 函数,只是做了一个类型转换:
    func toType(t *rtype) Type {
        if t == nil {
            return nil
        }
        return t
    }
    
    // 注意,返回值 `Type` 实际上是一个接口,定义了很多方法,用来获取类型相关的各种信息,而 `*rtype` 实现了 `Type` 接口。
    // Type 定义了非常多的方法,通过它们可以获取类型的一切信息,大家一定要完整的过一遍下面所有的方法。
    type Type interface {
        // 所有的类型都可以调用下面这些函数
      
        // 此类型的变量对齐后所占用的字节数
        Align() int
        // 如果是 struct 的字段,对齐后占用的字节数
        FieldAlign() int
        // 返回类型方法集里的第 `i` (传入的参数)个方法
        Method(int) Method
        // 通过名称获取方法
        MethodByName(string) (Method, bool)
        // 获取类型方法集里导出的方法个数
        NumMethod() int
        // 类型名称
        Name() string
        // 返回类型所在的路径,如:encoding/base64
        PkgPath() string
        // 返回类型的大小,和 unsafe.Sizeof 功能类似
        Size() uintptr
        // 返回类型的字符串表示形式
        String() string
        // 返回类型的类型值 —— 底层类型
        Kind() Kind
        // 类型是否实现了接口 u
        Implements(u Type) bool
        // 是否可以赋值给 u
        AssignableTo(u Type) bool
        // 是否可以类型转换成 u
        ConvertibleTo(u Type) bool
        // 类型是否可以比较
        Comparable() bool
      
        // 下面这些函数只有特定类型可以调用
        // 如:Key, Elem 两个方法就只能是 Map 类型才能调用
      
        // 类型所占据的位数
        Bits() int
        // 返回通道的方向,只能是 chan 类型调用
        ChanDir() ChanDir
        // 返回类型是否是可变参数,只能是 func 类型调用
        // 比如 t 是类型 func(x int, y ... float64)
        // 那么 t.IsVariadic() == true
        IsVariadic() bool
        // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用  
        // 这个很常用,比如变量 test 原类型为 []int , reflect.TypeOf(test).Elem() 会返回 int(即为子元素的类型)
        Elem() Type
    
        // 返回结构体类型的第 i 个字段,只能是结构体类型调用
        // 如果 i 超过了总字段数,就会 panic
        Field(i int) StructField
        // 返回嵌套的结构体的字段
        FieldByIndex(index []int) StructField
        // 通过字段名称获取字段
        FieldByName(name string) (StructField, bool)
        // FieldByNameFunc returns the struct field with a name
        // 返回名称符合 func 函数的字段
        FieldByNameFunc(match func(string) bool) (StructField, bool)
        // 获取函数类型的第 i 个参数的类型
        In(i int) Type
        // 返回 map 的 key 类型,只能由类型 map 调用
        Key() Type
        // 返回 Array 的长度,只能由类型 Array 调用
        Len() int
        // 返回类型字段的数量,只能由类型 Struct 调用
        NumField() int
        // 返回函数类型的输入参数个数
        NumIn() int
        // 返回函数类型的返回值个数
        NumOut() int
        // 返回函数类型的第 i 个值的类型
        Out(i int) Type
        // 返回类型结构体的相同部分
        common() *rtype
        // 返回类型结构体的不同部分
        uncommon() *uncommonType
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    2.2 ValueOf

    讲完了 TypeOf 函数,再来看一下 ValueOf 函数。返回值 reflect.Value 表示 interface{} 里存储的实际变量,它能提供实际变量的各种信息。相关的方法常常是需要结合类型信息和值信息。例如,如果要提取一个结构体的字段信息,那就需要用到 _type (具体到这里是指 structType) 类型持有的关于结构体的字段信息、偏移信息,以及 *data 所指向的内容 —— 结构体的实际值。

    源码如下:

    func ValueOf(i interface{}) Value {
        if i == nil {
            return Value{}
        }
    
       // ……
        return unpackEface(i)
    }
    
    // 分解 eface
    // 从源码看,比较简单:将先将 `i` 转换成 `*emptyInterface` 类型, 
    // 再将它的 `typ` 字段和 `word` 字段以及一个标志位字段组装成一个 `Value` 结构体,而这就是 `ValueOf` 函数的返回值,
    // 它包含类型结构体指针、真实数据的地址、标志位。
    func unpackEface(i interface{}) Value {
        e := (*emptyInterface)(unsafe.Pointer(&i))
    
        t := e.typ
        if t == nil {
            return Value{}
        }
    
        f := flag(t.Kind())
        if ifaceIndir(t) {
            f |= flagIndir
        }
        return Value{t, e.word, f}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:

    // 设置切片的 len 字段,如果类型不是切片,就会panic
     func (v Value) SetLen(n int)
    
     // 设置切片的 cap 字段
     func (v Value) SetCap(n int)
    
     // 设置字典的 kv
     func (v Value) SetMapIndex(key, val Value)
    
     // 返回切片、字符串、数组的索引 i 处的值
     func (v Value) Index(i int) Value
    
     // 根据名称获取结构体的内部字段值
     func (v Value) FieldByName(name string) Value
    
     // ……
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Value 字段还有很多其他的方法。例如:

    // 用来获取 int 类型的值
    func (v Value) Int() int64
    
    // 用来获取结构体字段(成员)数量
    func (v Value) NumField() int
    
    // 尝试向通道发送数据(不会阻塞)
    func (v Value) TrySend(x reflect.Value) bool
    
    // 通过参数列表 in 调用 v 值所代表的函数(或方法
    func (v Value) Call(in []Value) (r []Value) 
    
    // 调用变参长度可变的函数
    func (v Value) CallSlice(in []Value) []Value
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    不一一列举了,反正是非常多。可以去 src/reflect/value.go 去看看源码,搜索 func (v Value) 就能看到。

    另外,通过 Type() 方法和 Interface() 方法可以打通 interfaceTypeValue 三者。

    • Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。
    • Interface() 方法可以将 Value 还原成原来的 interface。
    • 见下图
    img

    三、golang 反射注意

    1. golang 反射不能获取和修改 私有(小写字母)的属性以及方法

    2. ValueOf(*ptr) 方法传递的参数必须是 指针类型 才可以修改字段否则会报错

    四、Type 和 TypeOf

    reflect.Type 类型是一个接口类型,内部指定了若干方法,通过这些方法我们可以获取到反射类型的各种信息,例如:字段、方法等

    使用 *reflect.TypeOf()* 函数可以获取将任意值的类型对象 (reflect.Type),程序通过类型对象可以访问任意值的类型信息

    1. 理解 Type 和 种类 Kind

    reflect.Type 是变量的类型,而不是追根溯源的最底层类型

    type MyInt int
    reflect.TypeOf(MyInt).Kind()
    
    • 1
    • 2

    这里的 reflect.Type 就是 MyInt,而非 int,如果想获得 int 只能使用Kind()

    总结:*Type 表示的是静态类型,而 kind 表示的是底层真实的类型*

    2. 获取类型名以及 kind

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    // 定义一个 MyInt 类型
    type MyInt int
    
    func main() {
        // 声明一个空结构体
        type cat struct {
        }
        // 获取结构体实例的反射类型对象
        typeOfCat := reflect.TypeOf(cat{})
        // 显示反射类型对象的名称和种类
        fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
        // 获取Zero常量的反射类型对象
        typeOfA := reflect.TypeOf(Zero)
        // 显示反射类型对象的名称和种类
        fmt.Println(typeOfA.Name(), typeOfA.Kind())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    代码输出如下:

    cat struct

    MyInt int

    3. Type 常用方法

    获取与成员相关的方法如下:

    方法描述
    Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机
    NumField() int返回结构体成员字段数量(包含私有字段)
    FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false
    FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值

    StructField结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等

    type StructField struct {
        Name      string    // 字段名
        PkgPath   string    // 字段路径
        Type      Type      // 字段反射类型对象
        Tag       StructTag // 字段的结构体标签
        Offset    uintptr   // 字段在结构体中的相对偏移
        Index     []int     // Type.FieldByIndex中的返回的索引值
        Anonymous bool      // 是否为匿名字段
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4. 获取成员反射信息

    package main
    import (
        "fmt"
        "reflect"
    )
    func main() {
        // 声明一个空结构体
        type cat struct {
            Name string
            // 带有结构体tag的字段
            Type int `json:"type" id:"100"`
        }
        // 创建cat的实例
        ins := cat{Name: "mimi", Type: 1}
        // 获取结构体实例的反射类型对象
        typeOfCat := reflect.TypeOf(ins)
        // 遍历结构体所有成员
        for i := 0; i < typeOfCat.NumField(); i++ {
            // 获取每个成员的结构体字段类型
            fieldType := typeOfCat.Field(i)
            // 输出成员名和tag
            fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)
        }
        // 通过字段名, 找到字段类型信息
        if catType, ok := typeOfCat.FieldByName("Type"); ok {
            // 从tag中取出需要的tag
            fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    5. 通过类型信息创建实例

    当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针

    func main() {
        var a int
        // 取变量a的反射类型对象
        typeOfA := reflect.TypeOf(a)
        // 根据反射类型对象创建类型实例
        aIns := reflect.New(typeOfA)
        // 输出:*int ptr
        fmt.Println(aIns.Type(), aIns.Kind())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    五、Value 和 ValueOf

    reflect.Value 类型是一个结构体,封装了反射对象的值,内部若干方法,可以通过这些方法来获取和修改对象的值,使用 reflect.ValueOf 函数可以返回 Value 类型,value 类型还可以生成原始类型对象

    1. 生成原始类型的对象

    可以通过下面几种方法从反射值对象 reflect.Value 中获取原值

    方法名说 明
    Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
    Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
    Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
    Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
    Bool() bool将值以 bool 类型返回
    Bytes() []bytes将值以字节数组 []bytes 类型返回
    String() string将值以字符串类型返回

    代码演示如下👇

    func main() {
        // 声明整型变量a并赋初值
        var a int = 1024
        // 获取变量a的反射值对象
        valueOfA := reflect.ValueOf(a)
        // 获取interface{}类型的值, 通过类型断言转换
        var getA int = valueOfA.Interface().(int)
        // 获取64位的值, 强制类型转换为int类型
        var getA2 int = int(valueOfA.Int())
        fmt.Println(getA, getA2)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 操作结构体成员的值

    反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,方法列表参考 Type 常用方法

    修改成员的值 使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果该对象不可寻址或者成员是私有的,则无法修改对象值

    判定是否可以操作的方法有如下👇

    方法名描 述
    Elem() Value取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value
    Addr() Value对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
    CanAddr() bool表示值是否可寻址
    CanSet() bool返回值能否被修改。要求值可寻址且是导出的字段

    修改的方法如下👇

    Set(x Value)将值设置为传入的反射值对象的值
    Setlnt(x int64)使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
    SetUint(x uint64)使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
    SetFloat(x float64)使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
    SetBool(x bool)使用 bool 设置值。当值的类型不是 bod 时会发生宕机
    SetBytes(x []byte)设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
    SetString(x string)设置字符串值。当值的类型不是 string 时会发生宕机

    代码案例如下

    func main() {
        type dog struct {
            LegCount int
            age int
        }
        // 获取dog实例地址的反射值对象
        valueOfDog := reflect.ValueOf(&dog{})
        // 取出dog实例地址的元素
        valueOfDog = valueOfDog.Elem()
        // 获取legCount字段的值
        vLegCount := valueOfDog.FieldByName("LegCount")
        vAge := valueOfDog.FieldByName("age")
        // 尝试设置legCount的值
        vLegCount.SetInt(4)
        // 这里会报错
        vAge.SetInt(4)
        fmt.Println(vLegCount.Int())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    六、通过反射调用函数

    如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数,使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回

    package main
    import (
        "fmt"
        "reflect"
    )
    // 普通函数
    func add(a, b int) int {
        return a + b
    }
    func main() {
        // 将函数包装为反射值对象
        funcValue := reflect.ValueOf(add)
        // 构造函数参数, 传入两个整型值
        paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
        // 反射调用函数
        retList := funcValue.Call(paramList)
        // 获取第一个返回值, 取整数值
        fmt.Println(retList[0].Int())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    七、通过反射调用对象中的方法

    如果反射值对象中具有方法时,可以通过反射调用方法,获取方法如下👇

    方法描述
    Method(i int) Value根据索引,返回索引对应的方法
    NumMethod() int返回结构体成员方法(包含私有)
    MethodByName(name string) Value根据给定字符串返回字符串对应的结构体方法
    package main
    
    import (
        "fmt"
        "reflect"
    )
    type Cat struct {
        Name string
    }
    
    func (c *Cat) Sleep() {
        fmt.Println("呜呜呜...")
    }
    
    func main() {
        cat := Cat{}
        valueOf := reflect.ValueOf(&cat)
        showMethod := valueOf.MethodByName("Show")
        showMethod.Call(nil)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    八、反射实现:map 转 struct

    func Map2Struct(m map[string]interface{}, obj interface{}) {
        value := reflect.ValueOf(obj)
    
        // obj 必须是指针且指针指向的必须是 struct
        if value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct {
            value = value.Elem()
            getMapName := func(key string) interface{} {
                for k, v := range m {
                    if strings.EqualFold(k, key) {
                        return v
                    }
                }
                return nil
            }
            // 循环赋值
            for i := 0; i < value.NumField(); i++ {
                // 获取字段 type 对象
                field := value.Field(i)
                if !field.CanSet() {
                    continue
                }
                // 获取字段名称
                fieldName := value.Type().Field(i).Name
                fmt.Println("fieldName -> ", fieldName)
                // 获取 map 中的对应的值
                fieldVal := getMapName(fieldName)
                if fieldVal != nil {
                    field.Set(reflect.ValueOf(fieldVal))
                }
            }
        } else {
            panic("must prt")
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    九、反射实现:struct 转 map

    func Struct2Map(obj interface{}) map[string]interface{} {
        value := reflect.ValueOf(obj)
    
        if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct {
            panic("must prt")
        }
        value = value.Elem()
        t := value.Type()
    
        // 创建 map
        resultMap := make(map[string]interface{})
    
        // 循环获取字段名称以及对应的值
        for i := 0; i < value.NumField(); i++ {
            val := value.Field(i)
            typeName := t.Field(i)
            if !val.CanSet() {
                resultMap[typeName.Name] = reflect.New(typeName.Type).Elem().Interface()
                continue
            }
            resultMap[typeName.Name] = val.Interface()
        }
    
        return resultMap
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    maven推送本地jar包到nexus仓库遇到的问题
    《Redis 设计与实现》笔记
    高通SDX12:ASoC 音频框架浅析
    Java Timer实战
    Nginx
    GO语言-区块链离线钱包开发之如何存储私钥
    虚拟机改IP地址
    Ubuntu18.04双系统 + ROS Melodic + RoboRTS安装教程
    Redis中SDS简单动态字符串
    基于Android健身预约系统APP开发
  • 原文地址:https://blog.csdn.net/qq_24433609/article/details/126787597