什么是 field tag

在 Go 的 struct 定义中,有时需要对某个字段添加额外的元信息,这就需要用到 field tag,即成员标签变量

field tag 常见于 JSON (或 XML)结构体的定义中。当用 Go 的 struct 方式定义一个 JSON 数据结构,其 struct 中定义的变量名与实际 JSON 所用的变量名不一定一致(在 Go 中,这类 struct 的变量都要求首字母大写以保持可被外部引用),这时就需要用 field tag 来为每个变量添加额外的元信息。如下所示:

1
2
3
4
5
6
7
8
type User struct {
  	Id        int       `json:"id"`
  	Name      string    `json:"name"`
  	Bio       string    `json:"about,omitempty"`
  	Active    bool      `json:"active"`
  	Admin     bool      `json:"-"`
  	CreatedAt time.Time `json:"created_at"`
}

其对应的 JSON 格式为:

1
2
3
4
5
6
7
{
	"id": 1,
  	"name": "John Doe",
  	"about": "Some Text",
  	"active": true,
  	"created_at": "2016-07-16T15:32:17.957714799Z"
}

field tag 的定义

成员标签定义可以是任意字符串,但是按照习惯,是由一串由空格分开的标签键值对 key:"value" 组成的。因为标签的值使用双引号括起来,所以一般标签都是原生的字符串字面量。

field tag 的使用

从本质上来看,field tag 其实是提供了一种机制去为 struct 成员变量添加额外的元信息,这部分元信息可以 reflect 的方式来获取。下面就来展示一下怎么用 Go 的 reflect 包来获取 field tag。

如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
	"fmt"
	"reflect"
)

type T struct {
	f1     string "f one" // 虽然不符合常用法,但这也是字符串,所以是合法的
	f2     string
	f3     string `f three`
	f4, f5 int64  `f four and five`          // f4 和 f5 都是一样的 field tag
	f6     bool   `json:"test1,test2,empty"` // 符合惯用法的定义
}

func main() {
	t := reflect.TypeOf(T{})
	f1, _ := t.FieldByName("f1")
	fmt.Printf("Field Name: %s, Field Tag: %s\n", f1.Name, f1.Tag)

	f2, _ := t.FieldByName("f2")
	fmt.Printf("Field Name: %s, Field Tag: %s\n", f2.Name, f2.Tag) // 没有 field tag,所以为空

	f3, _ := t.FieldByName("f3")
	fmt.Printf("Field Name: %s, Field Tag: %s\n", f3.Name, f3.Tag)

	f4, _ := t.FieldByName("f4")
	fmt.Printf("Field Name: %s, Field Tag: %s\n", f4.Name, f4.Tag)

	f5, _ := t.FieldByName("f5")
	fmt.Printf("Field Name: %s, Field Tag: %s\n", f5.Name, f5.Tag)

	f6, _ := t.FieldByName("f6")
	fmt.Printf("Field Name: %s, Field Tag: %s\n", f6.Name, f6.Tag)
}

将输出:

1
2
3
4
5
6
Field Name: f1, Field Tag: f one
Field Name: f2, Field Tag: 
Field Name: f3, Field Tag: f three
Field Name: f4, Field Tag: f four and five
Field Name: f5, Field Tag: f four and five
Field Name: f6, Field Tag: json:"test1,test2,empty"

reflect 包支持对采用惯用法定义的 struct tag 的解析,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"fmt"
	"reflect"
)

type T struct {
	f string `one:"1" two:"2" blank:""`
}

func main() {
	t := reflect.TypeOf(T{})
	f, _ := t.FieldByName("f")
	fmt.Println(f.Tag)

	v, ok := f.Tag.Lookup("one")
	fmt.Printf("%s, %t\n", v, ok)

	v, ok = f.Tag.Lookup("two")
	fmt.Printf("%s, %t\n", v, ok)

	v, ok = f.Tag.Lookup("blank")
	fmt.Printf("%s, %t\n", v, ok)

	v, ok = f.Tag.Lookup("nothing")
	fmt.Printf("%s, %t\n", v, ok)
}

将输出:

1
2
3
4
5
one:"1" two:"2" blank:""
1, true
2, true
, true
, false

使用 go vet 工具可以检查 struct tag 是否有以惯用法定义:

1
$ go vet main.go

参考资料