struct标签在Go中有什么用途?

Go语言规范中,它提到了标签的简要概述:

字段声明后面可以跟着一个可选的字符串字面值标签, 中所有字段的属性 字段声明。标签通过反射可见 接口,否则将被忽略
// A struct corresponding to the TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers.
struct {
microsec  uint64 "field 1"
serverIP6 uint64 "field 2"
process   string "field 3"
}

这是一个非常简短的解释,我想知道是否有人可以提供给我这些标签有什么用?

144767 次浏览

下面是一个非常简单的例子,标签与encoding/json包一起使用,以控制在编码和解码期间如何解释字段:

尝试现场:http://play.golang.org/p/BMeR8p1cKf

package main


import (
"fmt"
"encoding/json"
)


type Person struct {
FirstName  string `json:"first_name"`
LastName   string `json:"last_name"`
MiddleName string `json:"middle_name,omitempty"`
}


func main() {
json_string := `
{
"first_name": "John",
"last_name": "Smith"
}`


person := new(Person)
json.Unmarshal([]byte(json_string), person)
fmt.Println(person)


new_json, _ := json.Marshal(person)
fmt.Printf("%s\n", new_json)
}


// *Output*
// &{John Smith }
// {"first_name":"John","last_name":"Smith"}

json包可以查看字段的标签,并被告知如何映射json <=>结构字段,以及额外的选项,如在序列化回json时是否应该忽略空字段。

基本上,任何包都可以在字段上使用反射来查看标记值并对这些值进行操作。在reflect包
中有更多关于它们的信息 # EYZ0: < / p >
按照惯例,标签字符串是一个可选的连接 空格分隔键:“value”对。每个键都是非空字符串 由空格以外的非控制字符(U+0020 ' ')组成, 引号(U+0022 ' ' '),冒号(U+003A ':')。每个值都引用使用

. U+0022 '"'字符和Go字符串文字语法

字段的标记允许您将元信息附加到可以使用反射获取的字段。通常,它用于提供关于如何将结构字段编码为或从另一种格式(或存储/从数据库检索)解码的转换信息,但您可以使用它存储您想存储的任何元信息,可以用于另一个包,也可以用于您自己的使用。

正如在reflect.StructTag的文档中提到的,按照惯例,标签字符串的值是由key:"value"对的空格分隔的列表,例如:

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

key通常表示后续的"value"所对应的包,例如json键由encoding/json包处理/使用。

如果要在"value"中传递多个信息,通常用逗号(',')分隔。

Name string `json:"name,omitempty" xml:"name"`

通常,"value"的破折号值('-')表示将该字段从进程中排除(例如,在json的情况下,它意味着不编组或反编组该字段)。

使用反射访问自定义标记的示例

我们可以使用反射(reflect包)来访问结构字段的标记值。基本上,我们需要获取结构的Type,然后我们可以查询字段,例如使用Type.Field(i int)Type.FieldByName(name string)。这些方法返回一个值StructField,它描述/表示一个结构字段;StructField.Tag是一个类型为Type0的值,它描述/表示一个标记值。

之前我们讨论过“convention"。这个约定意味着如果您遵循它,您可以使用StructTag.Get(key string)方法来解析标记的值,并返回您指定的key"value"公约被实现/内置于Get()方法中。如果您不遵循约定,Get()将无法解析key:"value"对并找到您要查找的内容。这也不是问题,但是您需要实现自己的解析逻辑。

还有StructTag.Lookup()(在Go 1.7中添加),即Get()类似,但将不包含给定键的标签与将空字符串与给定键关联的标签区分开来。

让我们看一个简单的例子:

type User struct {
Name  string `mytag:"MyName"`
Email string `mytag:"MyEmail"`
}


u := User{"Bob", "bob@mycompany.com"}
t := reflect.TypeOf(u)


for _, fieldName := range []string{"Name", "Email"} {
field, found := t.FieldByName(fieldName)
if !found {
continue
}
fmt.Printf("\nField: User.%s\n", fieldName)
fmt.Printf("\tWhole tag value : %q\n", field.Tag)
fmt.Printf("\tValue of 'mytag': %q\n", field.Tag.Get("mytag"))
}

输出(在去操场上尝试):

Field: User.Name
Whole tag value : "mytag:\"MyName\""
Value of 'mytag': "MyName"


Field: User.Email
Whole tag value : "mytag:\"MyEmail\""
Value of 'mytag': "MyEmail"

GopherCon 2015有一个关于struct标签的演讲叫做:

Struct标签的多个面(幻灯片)(和视频)

下面是一些常用的标签键:

它是一种规范,指定包如何处理带标记的字段。

例如:

type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}

json标签通知json包编组以下用户的输出

u := User{
FirstName: "some first name",
LastName:  "some last name",
}

会是这样的:

{"first_name":"some first name","last_name":"some last name"}

另一个例子是gorm包标签声明了数据库迁移必须如何完成:

type User struct {
gorm.Model
Name         string
Age          sql.NullInt64
Birthday     *time.Time
Email        string  `gorm:"type:varchar(100);unique_index"`
Role         string  `gorm:"size:255"` // set field size to 255
MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
Num          int     `gorm:"AUTO_INCREMENT"` // set num to auto incrementable
Address      string  `gorm:"index:addr"` // create index with name `addr` for address
IgnoreMe     int     `gorm:"-"` // ignore this field
}

在这个例子中,对于带gorm标签的Email字段,我们声明数据库中email字段的对应列必须为varchar类型,最大长度为100,并且它还必须有唯一的索引。

另一个例子是binding标签,它主要在gin包中使用。

type Login struct {
User     string `form:"user" json:"user" xml:"user"  binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}




var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

本例中的绑定标记向gin包提示,发送到API的数据必须具有用户和密码字段,因为这些字段是根据需要标记的。

因此,一般来说,标签是包需要知道如何处理不同结构类型的数据的数据,熟悉包需要的标签的最好方法是完全阅读包文档。