golang结构体标签与反射

本文对golang反射做了一些尝试,整理了一些关键理解。

结构体标签(structure tag)

用过golang json的同学应该对下面的结构体定义很熟悉:

json:"xxx"就是一段结构体标签,它本身并没有什么魔法,很像一段代码注释。

但是和注释的区别在于,通过反射机制是可以获取到标签的,从而可以实现一些神奇的事情。

反射与标签

我们平时用json marshal来序列化一个结构体,那么背后大概是什么原理呢?

我来实现一段演示用的代码,主要展示反射与标签的关系,并不是真的json序列化,这个函数叫做MyJsonEncode:

我定义了一个Request对象,将指针传了进去,当然也可以传对象进去,因为我的函数定义是这样的:

就像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{}这个万能容器中,放的到底是int、string、*int、*string还是什么其他类型的变量,因此需要用到反射机制。

通过TypeOf可以获取interface{}容纳的变量的类型,ValueOf就是取其中的变量值了。

我们使用json.Marshal的时候,一定会发现无论传入的是结构体对象还是结构体指针,都可以编码成json,这到底是为什么?

其实可以通过反射来得知变量的类型,如果是指针就取指针指向的值对象,所以总是相当于传入了一个对象:

这里Kind()返回一个枚举值,表示变量是什么类型,这里Ptr是指针的意思。

如果变量是指针,我们还需要进一步判断一下指针是否为空,我们之前说过interface{}为nil与interface{}装着空指针的区别了!

如果指针不空,那么通过Elem()可以取得指针的值类型与值对象,相当于*ptr,大家感受一下:

  • 原本objType是*int类型,那么objType.Elem()就是int类型
  • 原本objValue是一个*int变量,那么objValue.Elem()就是int变量

嵌套则递归

得到了值对象后,需要判断它是否为结构体,如果是结构体则需要递归为每个字段做json编码:

如果是结构体则代码继续向下运行,开始编码结构体的各个字段:

objType反射了结构体的定义,所以可以遍历它的每个字段(field),每个字段的类型赋值给field,值则通过objValue才能获得,我们已经说过Interface{}的类型与值!

json.Marshal只会导出首字母大写的字段,我们需要根据字段的首字母判断:

接着,我们打印出这个结构体字段的信息:

通过对field取Tag就可以得到标签的内容:

我们应该判定一下,只有标签中包含json标识的字段,才会被导出。

这里我们知道了结构体每个字段的导出名,那么剩下的工作就是递归的编码字段value:

字段value的interface()方法可以把字段的值(无论是指针、对象)包装到一个interface{}容器中返回,因此我们可以再次进入递归,处理这个子value。

结束

上述演示代码在github观看:https://github.com/owenliang/go-structure-tag/tree/master。

一些基本的反射API和编程逻辑就是这样了~

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~