Go中标签的用途是什么?


392

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"
}

这是IMO的简短说明,我想知道是否有人可以为我提供这些标签的用途?


我有一个的“语义”的评论使用了相关的问题:stackoverflow.com/questions/53101458/...
布鲁斯·亚当斯

Answers:


641

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

如的文档所述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描述/表示一个struct字段;并且StructField.TagStructTag描述/表示标记值的类型值。

以前我们谈论过“惯例”。该公约的手段,如果你遵循它,你可以使用StructTag.Get(key string)它解析变量的值,并返回该方法"value"key指定。该公约实施/内置到这个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"))
}

输出(在Go Playground上尝试):

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标签的演示,名为:

结构标签的许多面孔(幻灯片)(和视频

以下是常用标签键的列表:


28
极好的答案。这里的信息比拥有十倍业力的信息更有用。
Darth Egregious 2015年

2
非常好的总结!
stevenferrer

2
真棒的答案
艾伯托·梅贾

1
好答案!谢谢!
JumpAlways

1
惊人的答案,谢谢您提供所有这些信息!
山姆·霍姆斯

157

这是一个与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 <=> struct字段,以及其他选项,例如在序列化回json时是否应忽略空字段。

基本上,任何包都可以在字段上使用反射来查看标记值并对其执行操作。在反射包
http://golang.org/pkg/reflect/#StructTag中有关于它们的更多信息:

按照惯例,标记字符串是由空格分隔的键:“值”对的串联。每个键都是一个非空字符串,由空格(U + 0020''),引号(U + 0022'“')和冒号(U + 003A':')以外的非控制字符组成。每个值都用引号引起来。使用U + 0022'“'字符和Go字符串文字语法。


6
有点像Java注释?
伊斯梅尔·巴达维

7
@isbadawi:我不是Java的人,但是快速浏览一下Java注释的定义,是的,看来它们正在实现相同的目标;将元数据附加到可以在运行时检查的元素。
jdi 2012年

15
并不是真正的Java注释。Java批注是类型安全的,并且会在编译时检查-而不是像go这样的字符串文字。Java注释比golang基本元数据提供的功能强大得多。
2014年

2
作为Go的MongoDB驱动程序的一部分,mgo还在其bson包中使用了标签(也可以单独使用)。它使您可以精确控制生成的BSON。参见godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno

1
除了JSON和BSON之外,还有其他示例吗?
Max Heiber 2015年

1

这是某种规范,用于指定程序包如何处理带有标记的字段。

例如:

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
}

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

另一个示例是包装bindinggin最常使用的标签。

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的数据必须具有用户和密码字段,因为这些字段已按要求进行了标记。

因此,一般性的标签是程序包需要了解的数据,它们应如何处理不同结构类型的数据,而要完全了解程序包所需的标签的最佳方法是完整地阅读包装文件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.