查看原文
其他

盘点一下结构体标签在Go中的应用

脚本之家 2022-05-10

The following article is from 网管叨bi叨 Author KevinYan11

 关注
“脚本之家
”,与百万开发者在一起

作者 | KevinYan11

出品 | 网管叨bi叨(ID:kevin_tech)

掌握了Go语言的朋友们应该都知道,在Go的结构体类型声明里面,字段声明后可以跟一个可选的字符串标签。

type User struct {
    Name string `json:"name"`
}

上面是一个标准的例子,Name字段声明中指定了标签json:"name" xml:"name" ,这个标签值看着有点类似Java程序里给类属性加的注解。

那么这些结构体标签有什么用途呢,我们随便写管用吗?我们平时工作中常用的结构体标签有哪些呢?我们能不能自己定义结构体标签?今天就带大家掰扯清楚这些问题!

结构体标签

Go语言允许我们通过结构体字段标签给一个字段附加可以被反射获取的”元信息“,正好我们上篇文章实战演示Go反射的使用方法和应用场景中讲了Go语言反射使用方法相关的内容,对反射不清楚的可以先去再复习一下。

通常情况下,结构体标签被用于提供结构体字段如何被编码为或者解码自另外一种格式的转换信息(或者是以何种形式被保存至/获取自数据库)。不过,你也可以用它存储任何你想要设置的”元信息“,供其他包或者自己使用。

使用规范

结构体标签在使用上通常是遵守下面三个规范。

结构体标签字符串的值是一个由空格分隔的 key:"value" 对列表,例如:

type User struct {
    Name string `json:"name" xml:"name"`
}

键,通常表示后面跟的“值”是被哪个包使用的,例如json这个键会被encoding/json包处理使用。如果要在“键”对应的“值”中传递多个信息,通常通过用逗号(',')分隔来指定,例如

Name string `json:"name,omitempty"`

按照惯例,如果一个字段的结构体标签里某个键的“值”被设置成了的破折号 ('-'),那么就意味着告诉处理该结构体标签键值的进程排除该字段。例如,把一个字段的标签设置成下面这样

Name string `json:"-"`

就以为进行JSON编码/解码时忽略Name这个字段。

怎么获取到结构体标签

从一开始我们就说结构体标签是给反射准备的,那么怎么在Go程序里用反射获得到字段的结构体标签呢?看了我们上一篇文章的同学,应该会知道,结构体字段类型相关的信息,在反射的世界里使用reflect.StructFiled这个类型表示的。

type StructField struct {
 Name string
 Type      Type      // field type
 Tag       StructTag // field tag string
  ......
}

如上所示,其中包含的Tag字段即代表了字段声明中的结构体标签信息。让我们通过自定义结构体标签的例子来演示一下怎么使用它在反射里读取到标签里的信息。

用反射获取到自定义的结构体标签

使用反射reflect包访问结构体字段的标签值,我们需要先获取到结构体的类型信息Type,然后使用Type.Field(i int)Type.FieldByName(name string),方法查询字段信息,这两个方法都会返回一个StructField类型的值,上面我们也说了它在反射的世界里用于描述一个结构体字段;而StructField.Tag 是一个StructTag 类型的值,它描述了字段的标签。

上面我们谈到了结构体标签的使用规范,如果遵循规范给字段设置了标签后,就可以使用StructTagGet方法解析标签的值并返回你指定的键的“值”。

func (tag StructTag) Get(key string) string

为了方便判断一个给定的key是否存在与标签中,StructTag还提供了一个Lookup方法

func (tag StructTag) Lookup(key string) (value string, ok bool)

Get方法不同的是,Lookup会通过返回的ok值告知给定key是否存在与标签中。

下面通过一个例子,演示下获取我们自定义标签的过程。

package main

import (
 "fmt"
 "reflect"
)

type User struct {
 Name  string `mytag:"MyName"`
 Email string `mytag:"MyEmail"`
}



func main() {
 u := User{"Bob""bob@mycompany.com"}
 t := reflect.TypeOf(u)

 for i := 0; i < t.NumField(); i++ {
  field := t.Field(i)
  fmt.Printf("Field: User.%s\n", field.Name)
  fmt.Printf("\tWhole tag value : %s\n", field.Tag)
  fmt.Printf("\tValue of 'mytag': %s\n", field.Tag.Get("mytag"))
 }
}

上面的程序会输出

Field: User.Name
        Whole tag value : mytag:"MyName"
        Value of 'mytag': MyName
Field: User.Email
        Whole tag value : mytag:"MyEmail"
        Value of 'mytag': MyEmail

常用的结构体标签键

常用的结构体标签Key,指的是那些被一些常用的开源包声明使用的结构体标签键。在这里总结了一些,都是一些我们平时会用到的包,它们是:

  • json:  由encoding/json 包使用,详见json.Marshal()的使用方法和实现逻辑。
  • xml :  由encoding/xml包使用,详见xml.Marshal()
  • bson:  由gobson包,和mongo-go包使用。
  • protobuf:  由github.com/golang/protobuf/proto 使用,在包文档中有详细说明。
  • yaml:  由gopkg.in/yaml.v2 包使用,详见yaml.Marshal()
  • gorm:  由gorm.io/gorm包使用,示例可以在GORM的文档中找到。

当然这里列的就是最常用的几个库他们提供给我们使用的结构体标签,欢迎大伙踊跃留言,补充一些自己平时用过的库提供给开发者使用的结构体标签

总结

这篇文章算是我们上一篇讲Go反射的一个实践方向的延伸介绍,如果你也想在自己的包里提供一些结构体标签键,让自己的包更易用些,除了看咱们这篇文章外,还可以去看看上面咱们介绍的几个类库,看它们的源码里是怎么应用的,现学现用!

  推荐阅读:

针对Linux服务器!用Go编写的勒索软件变种

Go 网络编程和 TCP 抓包实操

深度细节 | Go 的 panic 的三种诞生方式

Go 并发编程 — 结构体多字段的原子操作

写给想去字节写 Go 的你

每日打卡赢积分兑换书籍入口

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存