开发时会遇到,将json字符串转为struct结构体,这一过程需使用反射。

Go语言是静态编译类语言,定义变量时就已经知道其是什么类型,但是我们在使用过程中有时候会遇到参数时interface{}类型的参数。那么,调用时就可以传递任何类型的参数,那么在函数内部想要知道传递的是什么类型的参数就需要使用反射。

func Println(a ...interface{})(n int,err error){
  return FPrintln(os.Stdout,a...)
}

Go语言的反射定义,任何接口由两部分组成:接口的具体类型和具体类型对应的值。

Interface{} 是空接口,可表示任何类型,可把任何类型转换为空接口,它通常用于反射、类型断言、以减少重复代码,简化编程

  • reflect.Value:变量的值 ,通过reflect.ValueOf获取
  • reflect.Type:变量的类型,通过reflect.TypeOf获取
func main(){
  i := 2
  ival := reflect.valueOf(i)
  itype := reflect.TypeOf(i)
  fmt.Println(ival,itype)
}

reflect.Value

reflect 是一个结构体
type value struct{
    typ *type
    ptr unsafe.Pointer
    flag
}

获取原始类型

任意类型对象通过reflect.valueOf转为reflect.Value。那么,reflect.Value通过interface方法转回相对类型对象。

func main(){
    i := 3
    // int ==> reflect.Value
    iv := reflect.ValueOf(i)
    //reflect.value ==> int
  i1 := iv.interface().(int)
  fmt.Println(i1)
}

修改对应的值

通过反射修改在运行时已定义的变量

func main(){
    i := 3
    ipv := reflect.ValueOf(&i)
  ipv.Elem().SetInt(4)
  fmt.Println(ipv)
}

reflect.valueOf函数的参数是指针,指针找到对应的内存地址,再通过Elem方法获取指向的值,然后修改其值。

如果修改struct类型的变量值,如果修改?

//1、传递struct结构体指针,获取对应的reflect.Value
//2、Elem方法获取指针指向的值
//3、Field方法获取要修改的字段
//4、Set系列方法修改对应的值
func main(){
  
type person struct {
        Name string  //结构体字段必须是公有的,大写
        Age  int
    }
    stu := person{
        Name: "jasen",
        Age:  18,
    }

    stuRe := reflect.ValueOf(&stu)
    stuRe.Elem().Field(1).SetInt(20)

    fmt.Println("修改后年龄是", stu.Age)
}

注意:如果要修改 struct 结构体字段值的话,该字段需要是可导出的,而不是私有的,也就是该字段的首字母为大写。

当我讲person结构体中字段改成小写的时候,立马报错,大家可以尝试一下:

panic: reflect: reflect.flag.mustBeAssignable using value obtained using unexported field

获取底层类型

Go语言中,我们可通过关键字type声明很多自定义类型,这里的底层类型就是对应的基础类型(接口、结构体、指针......)

p := person{name:"nanlv", age: 18}
pRef := reflect.ValueOf(&p)
fmt.Println(pRef.Kind())
pval := reflect.ValueOf(p)
fmt.Println(pval.Kind())

//输出结果
//  pstr
//  struct

Kind方法返回一个Kind类型的值,它是一个常量,具体有哪些值可以通过查看源码

reflect.Type

当我们的场景需要操作变量的类型相关时,可通过使用reflect.TypeOf函数去获取变量类型

reflect.Type 是一个接口
type Type interface {

    Implements(u Type) bool //用于判断是否实现了接口 u
    AssignableTo(u Type) bool // 用于判断是否可赋值给变量u,即是否可以使用 = 
   ConvertibleTo(u Type) bool //用于判断是否可以转换成类型u,即是否可以进行类型转换
    Comparable() bool // 用于判断该类型是否可比较,即是否可使用关系运算符进行比较
 
    //以下这些方法和Value结构体的功能相同
   Kind() Kind

    Method(int) Method
   MethodByName(string) (Method, bool)
   NumMethod() int
   Elem() Type
   Field(i int) StructField
   FieldByIndex(index []int) StructField
   FieldByName(name string) (StructField, bool)
   FieldByNameFunc(match func(string) bool) (StructField, bool)
   NumField() int
 }

遍历结构体字段和方法

p := person{name:"nanlv", age: 18}
pt := reflect.TypeOf(&p)

// 遍历person字段
for i :=0; i< pt.NumField(); i++ {
  fmt.Println("字段:",pt.Field(i).Name)
}
// 遍历 person方法
for i :=0; i< pt.NumMethod(); i++ {
  fmt.Println("字段:",pt.pt.Method(i).Name)
}
  • FieldByName 获取指定的字段
  • MethodByName 获取指定的方法

JSON与Struct互转

Go语言的标准库json包,可以实现此功能

  • json.Marshal函数,struct转json
  • json.Unmarshal函数,json转struct

经常定义结构体时,字段是公有的,字段名都是大写的,但是我们json字符串是小写,这时候我们就会使用struct tag

// 使用struct tag
type person struct {
  Name string `json:"name"`  // `bson:"name"`
   Age int `json:"age"`
 }

// 通过Tag.Get("json")获取对应json的字段名
pt.Field(0).Tag.Get("json")

通过Tag.Get("json")获取对应json的字段名

Struct tag 可当作结构体字段的元数据配置,可使用场景:orm映射,xml转换,生成swagger文档等等