在Go语言中,我们可以给结构体的字段加一个标签(tag),Go Language Specification
中有一段简短的描述:
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. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.
文中有这样的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct {
x, y float64 "" // an empty tag string is like an absent tag
name string "any string is permitted as a tag"
_ [4]byte "ceci n'est pas un champ de structure"
}
// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
microsec uint64 `protobuf:"1"`
serverIP6 uint64 `protobuf:"2"`
}
|
tags允许我们为字段附加元信息(meta-information),这些元信息可以在反射中使用。比较常见的场景是使用tags来提供字段如何被编码为另一种格式或从另一种格式解码(或从数据库存储/检索)的转换信息,但是不局限于此,它可以被用来存储任何元信息,并且可以被其他包使用。
上边提到tag提供的元信息可以在反射中使用,在 reflect.StructTag 文档
中提到,tag的值是一个字符串,一般是一组以空格分隔的key: "value"
对,比如
1
2
3
4
|
struct {
Microsec uint64 `protobuf:"1" json:"microsec"`
ServerIP6 uint64 `protobuf:"2" json:"serverIP6"`
}
|
如果要在 "value"
中传递多个信息,通常用逗号(','
)隔开,比如
1
|
ServerIP6 uint64 `protobuf:"2" json:"serverIP6",omitempty`
|
"value"
可以使用破折号('-'
)来表示不处理该字段(例如,在json的情况下,意味着不对该字段进行marshal或unmarshal)。
我们先直接看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type Timestamp struct {
Microsec uint64 `protobuf:"1"`
ServerIP6 uint64 `protobuf:"2"`
}
ts := Timestamp{1644067146, 4254076641}
t := reflect.TypeOf(ts)
for _, fieldName := range []string{"Microsec", "ServerIP6"} {
field, found := t.FieldByName(fieldName)
if !found {
continue
}
fmt.Printf("\nField: Timestamp.%s\n", fieldName)
fmt.Printf("\tTag value : %q\n", field.Tag)
fmt.Printf("\tValue of 'protobuf': %q\n", field.Tag.Get("protobuf"))
}
|
输出为
1
2
3
4
5
6
7
|
Field: Timestamp.Microsec
Tag value : "protobuf:\"1\""
Value of 'protobuf': "1"
Field: Timestamp.ServerIP6
Tag value : "protobuf:\"2\""
Value of 'protobuf': "2"
|
首先我们需要获得struct的类型,然后可以通过 Type.Field(i int)
或 Type.FieldByName(name string)
等方法来获取字段数据。这些方法返回一个 StructField
类型的值,它描述了一个struct的字段;其中有一个StructField.Tag
类型的值叫 Tag
,本质上是一个字符串,它就描述了一个标签的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type StructField struct {
// Name is the field name.
Name string
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
type StructTag string
|
可以使用 StructTag.Get(key string)
方法来解析一个标签值并返回指定键的值,注意这需要在定义tag的时候遵循key:"value"
格式,如果是其他格式就需要自己实现解析逻辑。如果tag不存在,Get
函数会返回空字符串,也就是说如果一个key的值被设置成了空字符串,使用Get
是没法区分的,如果想区分,需要使用 StructTag.Lookup()
。
在一些库中,我们经常能看到tags的使用,比如
举个json的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type Timestamp struct {
Microsec uint64 `json:"microsec"`
ServerIP6 uint64 `json:"server_ip6"`
}
json_string := `
{
"microsec": 12345,
"server_ip6": 67890
}
`
ts := new(Timestamp)
json.Unmarshal([]byte(json_string), ts)
fmt.Println(ts)
newJson, _ := json.Marshal(ts)
fmt.Printf("%s\n", newJson)
|
输出为
1
2
|
&{12345 67890}
{"microsec":12345,"server_ip6":67890}
|
文章作者
杂毛小道
上次更新
2022-02-05
许可协议
署名 4.0 国际 (CC BY 4.0)