Golang结构(struct)用于在一个单元中收集多个信息片段。这些信息集合用于描述更高级的概念,例如由街道、城市、州和邮编组成的地址。当您从数据库或API等系统读取这些信息时,可以使用struct标记来控制如何将这些信息分配给struct的字段。
一个结构Struct的标签是什么样的?
Go struct标签是注释,出现在Go struct声明的类型之后。每个标记都由与某些相应值相关联的短字符串组成。
一个struct标签看起来像这样,带有反勾'字符的标签:
type User struct {
Name string `example:"name"`
}
其他的Go代码能够检查这些结构,并提取分配给它所请求的特定键的值。如果没有额外的代码来检查结构标记,则结构标记对代码的操作没有影响。
试试这个例子,看看struct标记是什么样子的,如果没有来自另一个包的代码调用,它们将没有任何效果。
package main
import "fmt"
type User struct {
Name string `example:"name"`
}
func (u *User) String() string {
return fmt.Sprintf("Hi! My name is %s", u.Name)
}
func main() {
u := &User{
Name: "Sammy",
}
fmt.Println(u)
}
输出结果:
Hi!My name is Sammy
这个例子定义了一个带有Name字段的User类型。Name字段被赋予了一个结构标记: example:"name"。在结构中,我们将这个特定的标签称为“example结构标签”,因为它使用单词“example”作为键。example标记了结构字段“Name”字段值为“name”。对于User类型,我们还定义了fmt所需的String()方法。当我们将类型传递给fmt时,会自动调用这String()个函数。并使我们有机会生成结构体的一个格式很好的版本。
在main函数体中,创建一个User类型的新实例,并将其传递给fmt.Println。尽管该结构有一个struct标记,但我们看到它对Go代码的操作没有影响。如果struct标记不存在,它的行为将完全相同。
为了使用struct标记完成某些事情,必须编写其他Go代码来在运行时检查struct。标准库有一些包使用struct标记作为其操作的一部分。其中最流行的是encoding/json包。
Encoding JSON
JavaScript对象表示法(JSON)是一种文本格式,用于对组织在不同字符串键下的数据集合进行编码。它通常用于不同程序之间的数据通信,因为它的格式足够简单,所以在许多不同语言中存在解码它的库。下面是JSON的一个例子:
{
"language": "Go",
"mascot": "Gopher"
}
这个JSON对象包含两个键:语言(language)和吉祥物(mascot)。这些键后面是相关的值。在这里,语言键的值为Go,吉祥物(mascot)的值为Gopher。
标准库中的JSON编码器使用struct标记作为注释,向编码器表明您希望如何在JSON输出中为字段命名。这些JSON编码和解码机制可以在encoding/ JSON包中找到。
试试下面这个例子,看看没有struct标签的JSON是如何编码的:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string
Password string
PreferredFish []string
CreatedAt time.Time
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
输出的结果如下:
{
"Name": "Sammy the Shark",
"Password": "fisharegreat",
"CreatedAt": "2019-09-23T15:50:01.203059-04:00"
}
我们定义了一个描述用户的结构,其字段包括用户的名称、密码和创建用户的时间。在main函数中,我们通过除PreferredFish以外的所有字段提供值来创建该用户的实例。然后我们将User的实例传递给json.MarshalIndent函数。这样我们就可以更容易地看到JSON输出,而无需使用外部格式化工具。这个调用可以用json. marshal (u)替换,以打印JSON而不需要任何额外的空格。MarshalIndent有两个附加参数:控制输出的前缀(我们在使用空字符串时省略了这个前缀)和用于缩进的字符,这里是两个空格字符。json.MarshalIndent产生的任何错误将被记录,程序使用os.Exit(1)终止。
最后,我们强制转换json返回的[]byte到一个字符串,并将结果字符串传递给fmt.Println用于在终端上打印。
结构的字段完全按照字段名称输出。不过,这并不是您所期望的典型JSON驼峰写法输出样式。在下一个示例中,您将更改字段的名称,使其遵循驼峰大小写样式。但这将输出一个空的json,因为struct字段命名与Go关于导出字段名的规则冲突。
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
name string
password string
preferredFish []string
createdAt time.Time
}
func main() {
u := &User{
name: "Sammy the Shark",
password: "fisharegreat",
createdAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
代码输出结果如下:
{}
在这个版本中,我们将字段的名称改为驼峰写法。现在Name是name, Password是password,最后CreatedAt是createdAt。在main主体中,我们改变了结构体的实例化,以使用这些新名称。但这次的输出是一个空JSON对象{}。
驼峰命名字段要求第一个字符小写。虽然JSON不关心如何命名字段,但Go关心,因为它指示了字段在包外的可见性。由于encoding/json包与我们使用的主包是一个独立的包,为了使它对encoding/json可见,所以必须将第一个字符大写,并且需要某种方式向JSON编码器传递我们希望这个字段被命名为什么。
使用Struct标记来控制编码
修改前面的示例,通过使用struct标记对每个字段进行注释,从而使用驼峰式大小写输出。encoding/json识别的struct标签有一个json键和一个控制输出的值。本例修复了前两次尝试:。
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish []string `json:"preferredFish"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
这次输出的结果如下:
{
"name": "Sammy the Shark",
"password": "fisharegreat",
"preferredFish": null,
"createdAt": "2019-09-23T18:16:17.57739-04:00"
}
通过将其名称的首字母大写,我们已经将结构字段更改为对其他包可见。然而,这次我们以json的形式添加了struct标签:“name”,其中“name”是我们想要输出到json的名称。
现在,我们已经成功地正确格式化了JSON。但是请注意,有些空值的字段会被打印出来。如果您愿意,JSON编码器也可以消除这些字段。
屏蔽JSON中空字段
通常会禁止输出struct中未设置的字段。因为Go中的所有类型都有一个“零值”,它们被设置为一些默认值,encoding/json包需要额外的信息来告诉一些字段应该被认为是未设置的。在任何json struct标记的值部分,您可以使用“omitempty”后缀,告诉json编码器,当字段被设置为零值时,该字段的输出将被忽略。下面的示例修复了前面的示例,使其不再输出空字段:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish []string `json:"preferredFish,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
输出的结果如下:
{
"name": "Sammy the Shark",
"password": "fisharegreat",
"createdAt": "2019-09-23T18:21:53.863846-04:00"
}
我们修改了前面的例子,使PreferredFish字段现在有了结构标签json:" PreferredFish,omitempty"。omitempty的存在会导致JSON编码器跳过该字段。在我们前面例子的输出中,它的值为null。
这个输出看起来好多了,但是我们仍然打印出了用户的密码。encoding/json包为我们提供了另一种完全忽略私有(敏感信息)字段的方法。
忽略私有字段
Struct中有些字段的性质可能是敏感的,因此在这些情况下,我们希望JSON编码器完全忽略字段—即使设置了字段。这是使用特殊的值“-”作为struct标签的值来完成的。
这个例子修复了暴露用户密码的问题。
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"-"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
运行这段代码,输出如下:
{
"name": "Sammy the Shark",
"createdAt": "2019-09-23T16:08:21.124481-04:00"
}
在这个例子中,与之前的例子相比,唯一改变的是密码字段现在使用了特殊的“-”值作为json: struct标签。在本例的输出中,密码字段不再出现。
encoding/json包的这些特性:omitempty、"-"和其他选项,它们都不是标准,包决定如何处理struct标记的值取决于它的实现。由于encoding/json包是标准库的一部分,其他包也按照惯例以相同的方式实现了这些特性。但是,阅读使用struct标记的任何第三方包的文档,了解支持哪些内容,不支持哪些内容,这是很重要的。
总结
Struct tag(结构标记)提供了一种强大的方法来增强与结构一起工作的代码的功能。许多标准库和第三方包提供了通过使用struct tag定制操作的方法。在代码中有效地使用它们既能提供这种定制行为,又能简洁地记录未来开发人员如何使用这些字段。
| 留言与评论(共有 0 条评论) “” |