介绍
这些信息集(https://andsky.com/tech/tutorials/defining-structs-in-go)用于描述更高层次的概念,如由街道,城市,州和邮政代码组成的地址。当您从数据库或API等系统中读取这些信息时,您可以使用结构标签来控制这些信息如何分配到结构的字段。
结构标签是什么样子?
Go struct 标签是 Go struct 声明中的键后出现的注释,每个标签由与某些相应值相关的短串组成。
一个 struct 标签看起来像这样,标签与 backtick ` 字符相抵消:
1type User struct {
2 Name string `example:"name"`
3}
其他 Go 代码可以检查这些结构并提取所要求的特定密钥的值。
试试这个例子,看看结构标签看起来是什么样子,如果没有来自另一个包的代码,它们就没有效果。
1package main
2
3import "fmt"
4
5type User struct {
6 Name string `example:"name"`
7}
8
9func (u *User) String() string {
10 return fmt.Sprintf("Hi! My name is %s", u.Name)
11}
12
13func main() {
14 u := &User{
15 Name: "Sammy",
16 }
17
18 fmt.Println(u)
19}
这将产生:
1[secondary_label Output]
2Hi! My name is Sammy
这个例子定义了一个用户类型,一个名称字段。这个名称字段被赋予了一个例子结构标签。在谈话中,我们将这个特定标签称为例子结构标签,因为它使用例子这个词作为其密钥。这个例子结构标签具有名称字段的名称值。在用户类型上,我们还定义了 String()方法,这是由fmt.Stringer接口要求的。
在主的体内,我们创建一个新的用户类型的实例,并将其传送到fmt.Println。尽管结构存在一个结构标签,但我们看到它对这个 Go 代码的运作没有影响。
要使用结构标签来完成某些事情,其他 Go 代码必须被写成以在运行时检查结构。 标准库有使用结构标签作为其操作的一部分的包。
JSON 编码
JavaScript Object Notation(JSON)是一个用于编码在不同的字符串密钥下组织的数据集的文本格式,它通常用于在不同的程序之间传输数据,因为格式足够简单,使库存在以解码它在许多不同的语言。
1{
2 "language": "Go",
3 "mascot": "Gopher"
4}
这个 JSON 对象包含两个密钥,即语言和面具。接着这些密钥是相关的值,这里的语言密钥具有Go的值,而面具则被分配为Gopher。
标准库中的 JSON 编码器使用结构标签作为注释,向编码器表示您希望如何在 JSON 输出中命名您的字段。
试试这个例子,看看 JSON 是如何编码的,没有 struktur 标签:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "os"
8 "time"
9)
10
11type User struct {
12 Name string
13 Password string
14 PreferredFish []string
15 CreatedAt time.Time
16}
17
18func main() {
19 u := &User{
20 Name: "Sammy the Shark",
21 Password: "fisharegreat",
22 CreatedAt: time.Now(),
23 }
24
25 out, err := json.MarshalIndent(u, "", " ")
26 if err != nil {
27 log.Println(err)
28 os.Exit(1)
29 }
30
31 fmt.Println(string(out))
32}
这将打印以下输出:
1[secondary_label Output]
2{
3 "Name": "Sammy the Shark",
4 "Password": "fisharegreat",
5 "CreatedAt": "2019-09-23T15:50:01.203059-04:00"
6}
我们定义了一种描述用户的结构,其中包括其姓名、密码和用户创建的时间。在主要函数中,我们通过为所有字段提供值来创建该用户的实例,除了PreferredFish(Sammy喜欢所有鱼)。然后我们将用户的实例传递到json.MarshalIndent函数中。这被用来使我们更容易地看到JSON输出,而无需使用外部格式化工具。这个调用可以用json.Marshal(u)来打印JSON,而没有任何额外的白空间。两个额外的参数被传送到json.MarshalIndent来控制输出前缀(我们已经放弃了空串),以及用于打印的字符,这里有两个空间字符。
结构字段的字段正如命名所示,但这不是您可能期望的典型 JSON 风格,它使用骆驼容器为字段名称。您将更改字段的名称以遵循骆驼案例风格在下一个示例中。
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "os"
8 "time"
9)
10
11type User struct {
12 name string
13 password string
14 preferredFish []string
15 createdAt time.Time
16}
17
18func main() {
19 u := &User{
20 name: "Sammy the Shark",
21 password: "fisharegreat",
22 createdAt: time.Now(),
23 }
24
25 out, err := json.MarshalIndent(u, "", " ")
26 if err != nil {
27 log.Println(err)
28 os.Exit(1)
29 }
30
31 fmt.Println(string(out))
32}
这将呈现以下产出:
1[secondary_label Output]
2{}
在本版本中,我们已经更改了这些字段的名称,以便将它们变为骆驼。现在Name是name,Password是password,最后CreatedAt是createdAt。在main的体内,我们已经更改了我们结构的实例化,以使用这些新名称。
Camel 封面字段需要第一个字符是较低的字符。虽然 JSON 并不在乎你如何命名你的字段,但Go 是这样,因为它表明了包外的字段的可见性。由于编码/json包是我们使用的主包的单独包,我们必须将第一个字符加大,以使其可见到编码/json。 似乎我们处于僵局之中。
使用结构标签来控制编码
您可以更改前一个示例,以便将用骆驼的域名正确编码的外部字段进行导出,通过使用结构标签对每个字段进行注释。编码/json识别的结构标签具有json的密钥和控制输出的值。
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "os"
8 "time"
9)
10
11type User struct {
12 Name string `json:"name"`
13 Password string `json:"password"`
14 PreferredFish []string `json:"preferredFish"`
15 CreatedAt time.Time `json:"createdAt"`
16}
17
18func main() {
19 u := &User{
20 Name: "Sammy the Shark",
21 Password: "fisharegreat",
22 CreatedAt: time.Now(),
23 }
24
25 out, err := json.MarshalIndent(u, "", " ")
26 if err != nil {
27 log.Println(err)
28 os.Exit(1)
29 }
30
31 fmt.Println(string(out))
32}
这将产生:
1[secondary_label Output]
2{
3 "name": "Sammy the Shark",
4 "password": "fisharegreat",
5 "preferredFish": null,
6 "createdAt": "2019-09-23T18:16:17.57739-04:00"
7}
然而,这次我们添加了json:name的结构标签,其中name是我们想要的json.MarshalIndent`在将我们的结构打印为JSON时使用的名称。
但是,请注意,即使我们没有设置这些值,也打印了某些值的字段。
删除空的 JSON 字段
由于 Go 中的所有类型都具有零值,其设置为某些默认值,因此编码/json包需要额外的信息,以便能够告诉您某些字段在假设这个零值时应该被视为不设置。在任何json结构标签的值部分中,您可以用omitempty来补充您的字段所需的名称,以便告诉 JSON 编码器在字段设置为零值时抑制该字段的输出。
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "os"
8 "time"
9)
10
11type User struct {
12 Name string `json:"name"`
13 Password string `json:"password"`
14 PreferredFish []string `json:"preferredFish,omitempty"`
15 CreatedAt time.Time `json:"createdAt"`
16}
17
18func main() {
19 u := &User{
20 Name: "Sammy the Shark",
21 Password: "fisharegreat",
22 CreatedAt: time.Now(),
23 }
24
25 out, err := json.MarshalIndent(u, "", " ")
26 if err != nil {
27 log.Println(err)
28 os.Exit(1)
29 }
30
31 fmt.Println(string(out))
32}
这个例子将产生:
1[secondary_label Output]
2{
3 "name": "Sammy the Shark",
4 "password": "fisharegreat",
5 "createdAt": "2019-09-23T18:21:53.863846-04:00"
6}
我们已经修改了以前的示例,以便PreferredFish字段现在具有结构标签json:preferredFish,omitempty``。
这个输出看起来好得多,但我们仍然打印用户的密码.‘编码/json’包为我们提供了完全忽略私人字段的另一种方式。
忽略私人领域
然而,这些字段的性质可能是敏感的,所以在这种情况下,我们希望JSON编码器即使在设置时也完全忽略该字段。
此示例解决了暴露用户密码的问题。
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "os"
8 "time"
9)
10
11type User struct {
12 Name string `json:"name"`
13 Password string `json:"-"`
14 CreatedAt time.Time `json:"createdAt"`
15}
16
17func main() {
18 u := &User{
19 Name: "Sammy the Shark",
20 Password: "fisharegreat",
21 CreatedAt: time.Now(),
22 }
23
24 out, err := json.MarshalIndent(u, "", " ")
25 if err != nil {
26 log.Println(err)
27 os.Exit(1)
28 }
29
30 fmt.Println(string(out))
31}
当您运行此示例时,您将看到此输出:
1[secondary_label Output]
2{
3 "name": "Sammy the Shark",
4 "createdAt": "2019-09-23T16:08:21.124481-04:00"
5}
在本示例中,我们从以前的示例中唯一改变的是,密码字段现在使用特殊的 "-" 值为其 json:` 结构标签。
这些编码/json包的功能――,omitempty,"-和其他选项(https://pkg.go.dev/encoding/json#Marshal)――不是标准。 一个包决定与结构标签的值做什么取决于其实施情况。 由于编码/json包是标准库的一部分,其他包也以类似的方式实现了这些功能。 然而,对于任何使用结构标签的第三方包来说,重要的是阅读文档,以了解支持和不支持的内容。
结论
结构标签提供了一个强大的方式来增加与结构工作代码的功能。许多标准库和第三方包提供了通过使用结构标签来定制其运作的方式。