本文对golang反射做了一些尝试,整理了一些关键理解。
结构体标签(structure tag)
用过golang json的同学应该对下面的结构体定义很熟悉:
type Request struct { Id int `json:"id"` Name string `json:"name"` inner string Age *int `json:"age"` Xi interface{} }
`json:”xxx”`就是一段结构体标签,它本身并没有什么魔法,很像一段代码注释。
但是和注释的区别在于,通过反射机制是可以获取到标签的,从而可以实现一些神奇的事情。
反射与标签
我们平时用json marshal来序列化一个结构体,那么背后大概是什么原理呢?
我来实现一段演示用的代码,主要展示反射与标签的关系,并不是真的json序列化,这个函数叫做MyJsonEncode:
func main() { var ( req *Request ) req = &Request{ Id: 1, Name: "owen", } MyJsonEncode(req) }
我定义了一个Request对象,将指针传了进去,当然也可以传对象进去,因为我的函数定义是这样的:
func MyJsonEncode(obj interface{}) { var ( i int objType reflect.Type objValue reflect.Value field reflect.StructField fieldValue reflect.Value fieldName string )
就像json.Marshal一样,interface{}可以容纳任意类型的变量,interface{}本质上内部维护了2个东西:
type:也就是变量的类型,比如是int类型,*int类型,都是不同的。
value:变量的值
再次理解interface{}
这里存在一个比较容易混淆的概念,大家一定要注意区分:
interface{}为nil:这是说interface{}没有容纳任何变量
interface{}装了空指针:比如把ptr *string = nil赋值给了obj interface{},这种情况下obj的type是*string,值是nil
所以,我们首先要判断interface{}为nil的情况,这种情况压根没法json编码:
// 接口是空(没装任何东西的interface{}) if obj == nil { fmt.Println("空接口") return }
反射类型与值
接下来,我们要判断一下interface{}这个万能容器中,放的到底是int、string、*int、*string还是什么其他类型的变量,因此需要用到反射机制。
// 反射变量 objType = reflect.TypeOf(obj) // 反射类型 objValue = reflect.ValueOf(obj) // 反射值
通过TypeOf可以获取interface{}容纳的变量的类型,ValueOf就是取其中的变量值了。
我们使用json.Marshal的时候,一定会发现无论传入的是结构体对象还是结构体指针,都可以编码成json,这到底是为什么?
其实可以通过反射来得知变量的类型,如果是指针就取指针指向的值对象,所以总是相当于传入了一个对象:
// 如果是指针, 需要取值 if objType.Kind() == reflect.Ptr { if objValue.IsNil() { // 空指针 fmt.Println("空指针") return } objType = objType.Elem() // 相当于类型为*ptr objValue = objValue.Elem() // 相当于值为*ptr }
这里Kind()返回一个枚举值,表示变量是什么类型,这里Ptr是指针的意思。
如果变量是指针,我们还需要进一步判断一下指针是否为空,我们之前说过interface{}为nil与interface{}装着空指针的区别了!
如果指针不空,那么通过Elem()可以取得指针的值类型与值对象,相当于*ptr,大家感受一下:
原本objType是*int类型,那么objType.Elem()就是int类型
原本objValue是一个*int变量,那么objValue.Elem()就是int变量
嵌套则递归
得到了值对象后,需要判断它是否为结构体,如果是结构体则需要递归为每个字段做json编码:
// 如果不是结构体, 则不需要递归处理 if objType.Kind() != reflect.Struct { fmt.Println("普通值", objValue.Interface()) return }
如果是结构体则代码继续向下运行,开始编码结构体的各个字段:
// 递归处理结构体中的字段 for i = 0; i < objType.NumField(); i++ { field = objType.Field(i) // 获取字段类型 fieldValue = objValue.Field(i) // 获取字段的值
objType反射了结构体的定义,所以可以遍历它的每个字段(field),每个字段的类型赋值给field,值则通过objValue才能获得,我们已经说过Interface{}的类型与值!
json.Marshal只会导出首字母大写的字段,我们需要根据字段的首字母判断:
// 小写字段不导出 fieldName = field.Name if unicode.IsLower(rune(fieldName[0])) { continue }
接着,我们打印出这个结构体字段的信息:
// 打印这个字段的信息 fmt.Println("字段:", field.Name, "类型:", field.Type, "标签:", field.Tag)
通过对field取Tag就可以得到标签的内容:
字段: Id 类型: int 标签: json:"id" 字段: Name 类型: string 标签: json:"name" 字段: Age 类型: *int 标签: json:"age" 字段: Xi 类型: interface {} 标签:
我们应该判定一下,只有标签中包含json标识的字段,才会被导出。
这里我们知道了结构体每个字段的导出名,那么剩下的工作就是递归的编码字段value:
// 递归编码这个字段 MyJsonEncode(fieldValue.Interface()) } // for循环结束
字段value的interface()方法可以把字段的值(无论是指针、对象)包装到一个interface{}容器中返回,因此我们可以再次进入递归,处理这个子value。
结束
上述演示代码在github观看:https://github.com/owenliang/go-structure-tag/tree/master。
一些基本的反射API和编程逻辑就是这样了~