结构体字段tag

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解释
jsonjson序列化或反序列化时字段的名称
dbsqlx模块中对应的数据库字段名
formgin框架中对应的前端的数据字段名
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。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值