Tags in Golang

12,899 阅读5分钟
type People struct {
	Name string `json:"name"`
	Age  int8   `json:"age"`
}

在学习过程中,看到类似上面的代码,一下子懵了个逼😳。。。大概一查,这是 Golang 中的 Tags 语法,官方解释是这样的:

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. The tags are made visible through a reflection interface but are otherwise ignored.

官方的解释不够接地气,像我这样的初学者看了等于是没看的,我们加点实际场景进去就能明白这到底能干吗。 由于 Golang 中对字段的标记可以在 反射 时获取到,所以通常是用来在将 struct 编码转换的过程中提供一些转换规则的信息,比如👇下面例子中提到的 JSON 转换。当然你也可以用它来存储你想要的任何其他元信息(Meta-information)。

举例子

举一个 API 的例子 🌰,获取用户ID为1的用户信息:GET /v1/user/1。简单的做法是在获取到数据库 user 表的一条记录后,以 JSON 格式返回。

type User struct {
	Id int
  Name string
  Age int8
}

如果不做任何处理,那么此时返回的数据应该是如下:

{"Id":1,"Name":"X.FLY","Age":24}

咦,为啥都给我返回驼峰型的属性名啊?谁让你声明 User 结构体的时候属性名就是这样呢。 那么把结构体属性名改成小写吧。🚫 不行🚫,因为这样这些属性就都变成了私有属性了,别忘了 Golang 里面可没有 public / private / protected 这些关键词,全靠首字母大小写来区分好不! 那咋办?用 Tag 啊 😂

我们为结构体属性加上标记:

type User struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
	Age  int8
}

故意不给 age 加标记以便看区别,JSON 格式输出如下:

{"id":1,"name":"X.FLY","Age":24}

这就是 Tag 的神奇之处,不会污染到主体,也不需要在每次输出 JSON 数据的时候人工处理(好吧,移除 public 等关键词是需要付出代价的😅)。

使用方法

Tag 可以是任何字符串,下面将的是常见形式,别想岔了🤭

一般来讲,Tag 都是以 key:"value" 这种键值对的形式,如果有多个键值对,则以空格分隔。

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

key 一般指的是要使用的包名,比如这里的 json 表示这个 Name 字段会被 encoding/json 包使用和处理。

如果有多个 value 信息要传入,那么通常使用英文逗号 , 进行分隔。

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

omitempty 表明如果这个字段的值在编解码时为空(Defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string)那么就忽略这个字段。还有一个常用的是 -,它表示直接忽略这个字段。

理解 Tag

前面说到 Tag 可以被 reflect 包获取到,其实除了通过反射机制能获取到 Tag 信息,对于其他方式这些标记都是不可见的! 我们先来看看官方 reflect 中 StructTag 部分,reflect - The Go Programming Language

StructTagstring 基本类型的别名:type StructTag string,约定俗成的规则是以 key:"value" 这样的键值对。(如果我不按照这种 约定 来做呢?当然也可以,但是这样做会导致 StructTag.Get() 方法解析不了你那脱俗的标记,那么你就只能自己实现自己的解析逻辑了,总之你高兴就好😏)

func (tag StructTag) Get(key string) string

Get() 函数用于获取指定 key 的 value,比如有一个标记是 mytag:"X.FLY",那么 Get("mytag") => X.FLY

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

Lookup() 函数是在 Golang 1.7 之后加的,用来判断是否存在指定的 key。

使用例子可以在 The Go Playground 上运行尝试,这里就对反射不多做展开了。

JSON’s Tag

这里着重举出 JSON 的 Struct Tag(1. JSON 输出很常见; 2. 可以以此类推其他如 XML’s Tag)。 我想知道 JSON 的 tag 有哪些,去哪找?去官网 JSON.Marshal 函数文档中找。

The encoding of each struct field can be customized by the format string stored under the "json" key in the struct field's tag. The format string gives the name of the field, possibly followed by a comma-separated list of options. The name may be empty in order to specify options without overriding the default field name. 我们会发现,在 JSON 编码过程中会去获取每一个 Struct field 的标记,从中拿取 key 为 json 的值,然后进行相应处理。

注意解析规则:value 的第一个字符串一定表示覆盖后的新字段名,后面如果有解析选项,则以英文逗号分隔。

比如 Name string json:"name,omitempty",第一个字符串 name 表示在编码后 Name 属性名就变成了 name。然后紧跟逗号分隔符,接着是 omitempty 选项。

  1. 如果我不想覆盖,只想加选项怎么办?Name string json:",omitempty",直接英文逗号打头。
  2. 极端一点,如果我的字段名就叫 Omitempty 呢?Omitempty string json:"omitempty,omitempty",记住第一个字符串表示的是新变量名,而不是选项,所以重名就重名好了,不怕🤪。

思考一下:- string json:"-,"- string json:",-" 有什么区别🧐?

  1. omitempty:如果字段的值为空(Defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string),那么在编码过程中就忽略掉这个字段。
  2. -:二话不说直接忽略该字段。
  3. string:将字段值在编码过程中转换成 JSON 中的字符串类型,只有当字段类型是 string, floating point, integer, or boolean 的情况下才会转换。

其他 Tag

常用的一些 Struct Tag 官方有列举,Well known struct tags · golang/go Wiki · GitHub

参考链接