Tag
方面一
什么是Tag
- Tag是结构体中某个字段别名,可以定义多个,空格分隔
type Student struct {
Name string `ak:"av" bk:"bv" ck:"cv"`
}
Tag的作用
- Tag相当于该字段的一个属性标签,Go语言中,一些包会通过tag来做相应判断
// 举个例子:
type Student struct {
Name string
}
// 现在将一个结构体实例化一个s1
s1 := Student {
Name: "s1",
}
// 将 s1 序列化
v, err := json.Marshal(s1) // json.Marshal方法:json序列化
if err != nil {
fmt.Println(err)
}
fmt.Println(string(v)) // []byte 转为 string,json
// 此时,string(v)为:
{
"Name": "s1"
}
因为在Go语言中,结构体字段想要为外部使用必须要大写,但是这个s1是返回给前端的,那每个字段都首字母大写会很怪,这时,可以给 Student 加 tag 解决
// 将结构体修改为:
type Student struct {
Name string `json:"name"`
}
序列化时,会自己找到名为json的tag,根据值来进行json后的赋值
// 所以,string(v)为:
{
"name": "s1"
}
常用tag记录
tag | 解释 |
---|---|
json | json序列化或反序列化时字段的名称 |
db | sqlx模块中对应的数据库字段名 |
form | gin框架中对应的前端的数据字段名 |
binding | 搭配form使用,默认如果没有查找到结构体中的某个字段则不报错值为空,binding为required代表没有找到返回错误给前端 |
xml | 针对xml解析包使用时的字段名 |
bson | 将数据存储到mongodb时使用 |
方面二
tag 是结构体的元信息,可以在运行时通过反射的机制读取出来,Tag在结构体字段的后方定义,由一对反引号包裹。
方面三
struct 成员变量标签(Tag)说明
-
需要先了解一下golang基础:在golang中,命名都是推荐使用驼峰方法,并且在首字母大小写有特殊语法含义:保外无法引用,但是又经常需要和其他的系统进行数据交互,比如:转为json格式,存儲到mongodb,这时候使用属性名作为键值不一定会符合项目要求
-
如果需要用到Tag中的内容,可以使用反射包(reflect)中的方法来获取
t := reflect.TypeOf(u)
field := t.Elem().Field(0)
fmt.println(field.Tag.Get("json"))
方面四
- Golang 中可以为结构体的字段添加tag,类似于Java中为类的属性添加注释。Golang本身的encoding/json 包解析json使用了tag,一些开源ORM框架也广泛使用了tag,通过代码实现tag解析,从而简化结构体字段的使用
package main
import (
"fmt"
"reflect"
"strings"
)
type Person struct {
Name string `label:"Person Name:" uppercase:"true"`
Age int `label:"Age is: "`
Sex string `label:"Sex is:"`
Description string
}
// 按照tag打印打印结构体
func PrintUseTag(ptr interface{}) error {
// 获取入参的类型
t := reflect.TypeOf(ptr)
// 入参类型校验
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
return fmt.Errorf("参数应为结构体指针类型")
}
fmt.Println(t.Elem())
// 取指针指向的结构体变量
v := reflect.ValueOf(ptr).Elem()
// 解析字段
for i := 0; i < v.NumField(); i ++ {
// 取tag
fieldInfo := v.Type().Field(i)
tag := fieldInfo.Tag
// 解析label tag
label := tag.Get("label")
if label == ""{
label = fieldInfo.Name + ": "
}
// 解析uppercase tag
value := fmt.Sprintf("%v", v.Field(i))
if fieldInfo.Type.Kind() == reflect.String {
uppercase := tag.Get("uppercase")
if uppercase == "true" {
value = strings.ToUpper(value)
} else {
value = strings.ToLower(value)
}
}
fmt.Println(label + value)
}
return nil
}
func main() {
person := Person{
Name: "Tom",
Age: 24,
Sex: "Male",
Description: "Cool",
}
PrintUseTag(&person)
}
方面五
type Request struct {
Id int `json:"id"`
Name string `json:"name"`
inner string
Age *int `json:"age"`
Xi interface{}
}
json:“xxx” 就是一段结构体标签,本身没有什么魔法,就像一段代码注释
与注释不同在于:通过反射机制可以获取到标签,从而实现一些神奇的事情。
反射与标签
- 问题:平时使用json marshal 来序列化一个结构体,背后大概什么原理?
// 一段演示代码,主要展示反射与标签的关系,并不是真的json序列化
func main() {
var (
req *Request
)
req = &Request{
Id : 1,
Name: "owner",
}
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{}本质上内部维护了两个东西
// 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 objType.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) // 获取字段的值
}
// json.Marshal 只会导出首字母大写的字段
// 根据首字母判断:小写字段不导出
fieldName = field.Name
if unicode.IsLower(rune(fieldName[0])) {
continue
}
// 打印结构体字段信息
fmt.Println("字段:", field.Name, "类型:", field.Type, "标签:", field.Tag)
- 字段value的interface方法可以把字段的值(无论是指针,对象)包装到一个interface{} 容器中返回,因此我们可以再次进入递归,处理这个value。