在Go语言中,结构标签的用途是什么?

huangapple go评论79阅读模式
英文:

What are the use(s) for struct tags in Go?

问题

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

字段声明后可以跟一个可选的字符串字面标签,该标签将成为相应字段声明中所有字段的属性。标签可以通过反射接口进行访问,但在其他情况下会被忽略。

// 与TimeStamp协议缓冲区对应的结构体。
// 标签字符串定义了协议缓冲区字段的编号。
struct {
    microsec  uint64 "field 1"
    serverIP6 uint64 "field 2"
    process   string "field 3"
}

在我看来,这是一个非常简短的解释,我想知道这些标签有什么用途?

英文:

In the Go Language Specification, it mentions a brief overview of tags:

> 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. The tags are made visible through a reflection
> interface but are otherwise ignored.
>
> // 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"
> }

This is a very short explanation IMO, and I was wondering if anyone could provide me with what use these tags would be?

答案1

得分: 993

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

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是一个StructTag类型的值,它描述/表示一个标签值。

之前我们谈到了“约定”。这个约定意味着如果您遵循它,您可以使用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"))
}

输出(在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有一个关于结构标签的演讲:

The Many Faces of Struct Tags (slide)(以及视频

这是一些常用的标签键列表:

英文:

A tag for a field allows you to attach meta-information to the field which can be acquired using reflection. Usually it is used to provide transformation info on how a struct field is encoded to or decoded from another format (or stored/retrieved from a database), but you can use it to store whatever meta-info you want to, either intended for another package or for your own use.

As mentioned in the documentation of reflect.StructTag, by convention the value of a tag string is a space-separated list of key:"value" pairs, for example:

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

The key usually denotes the package that the subsequent "value" is for, for example json keys are processed/used by the encoding/json package.

If multiple information is to be passed in the "value", usually it is specified by separating it with a comma (','), e.g.

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

Usually a dash value ('-') for the "value" means to exclude the field from the process (e.g. in case of json it means not to marshal or unmarshal that field).

Example of accessing your custom tags using reflection

We can use reflection (reflect package) to access the tag values of struct fields. Basically we need to acquire the Type of our struct, and then we can query fields e.g. with Type.Field(i int) or Type.FieldByName(name string). These methods return a value of StructField which describes / represents a struct field; and StructField.Tag is a value of type StructTag which describes / represents a tag value.

Previously we talked about "convention". This convention means that if you follow it, you may use the StructTag.Get(key string) method which parses the value of a tag and returns you the "value" of the key you specify. The convention is implemented / built into this Get() method. If you don't follow the convention, Get() will not be able to parse key:"value" pairs and find what you're looking for. That's also not a problem, but then you need to implement your own parsing logic.

Also there is StructTag.Lookup() (was added in Go 1.7) which is "like Get() but distinguishes the tag not containing the given key from the tag associating an empty string with the given key".

So let's see a simple example:

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

Output (try it on the 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 had a presentation about struct tags called:

The Many Faces of Struct Tags (slide) (and a video)

Here is a list of commonly used tag keys:

答案2

得分: 167

这是一个非常简单的示例,演示了如何使用encoding/json包中的标签来控制在编码和解码过程中如何解释字段:

试一试:http://play.golang.org/p/BMeR8p1cKf

<!-- language: go -->

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包中有更多关于标签的信息:
http://golang.org/pkg/reflect/#StructTag

按照约定,标签字符串是可选的以空格分隔的key:"value"对的串联。每个key是一个非空字符串,由非控制字符组成,除了空格(U+0020 ' ')、引号(U+0022 '"')和冒号(U+003A ':')。每个value使用U+0022 '"'字符引用,并使用Go字符串字面量语法。

英文:

Here is a really simple example of tags being used with the encoding/json package to control how fields are interpreted during encoding and decoding:

Try live: http://play.golang.org/p/BMeR8p1cKf

<!-- language: go -->

package main

import (
	&quot;fmt&quot;
	&quot;encoding/json&quot;
)

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

func main() {
	json_string := `
	{
		&quot;first_name&quot;: &quot;John&quot;,
		&quot;last_name&quot;: &quot;Smith&quot;
	}`
	
	person := new(Person)
	json.Unmarshal([]byte(json_string), person)
	fmt.Println(person)
	
	new_json, _ := json.Marshal(person)
	fmt.Printf(&quot;%s\n&quot;, new_json)
}

// *Output*
// &amp;{John Smith }
// {&quot;first_name&quot;:&quot;John&quot;,&quot;last_name&quot;:&quot;Smith&quot;}

The json package can look at the tags for the field and be told how to map json <=> struct field, and also extra options like whether it should ignore empty fields when serializing back to json.

Basically, any package can use reflection on the fields to look at tag values and act on those values. There is a little more info about them in the reflect package
http://golang.org/pkg/reflect/#StructTag :

> By convention, tag strings are a concatenation of optionally
> space-separated key:"value" pairs. Each key is a non-empty string
> consisting of non-control characters other than space (U+0020 ' '),
> quote (U+0022 '"'), and colon (U+003A ':'). Each value is quoted using
> U+0022 '"' characters and Go string literal syntax.

答案3

得分: 4

这是一些规范,用于指定如何处理带有标签的字段的包。

例如:

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"` // 将字段大小设置为255
  MemberNumber *string `gorm:"unique;not null"` // 将成员编号设置为唯一且非空
  Num          int     `gorm:"AUTO_INCREMENT"` // 将num设置为自增
  Address      string  `gorm:"index:addr"` // 为地址创建名为`addr`的索引
  IgnoreMe     int     `gorm:"-"` // 忽略此字段
}

在这个例子中,对于带有gorm标签的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
}

在这个例子中,binding标签提示gin包,发送到API的数据必须具有用户和密码字段,因为这些字段被标记为必需的。

总的来说,标签是包需要的数据,用于了解它们如何处理不同结构类型的数据,最好的方法是完全阅读包的文档。

英文:

It's some sort of specifications that specifies how packages treat with a field that is tagged.

for example:

type User struct {
	FirstName string `json:&quot;first_name&quot;`
	LastName string `json:&quot;last_name&quot;`
}

json tag informs json package that marshalled output of following user

u := User{
		FirstName: &quot;some first name&quot;,
		LastName:  &quot;some last name&quot;,
	}

would be like this:

{&quot;first_name&quot;:&quot;some first name&quot;,&quot;last_name&quot;:&quot;some last name&quot;}

other example is gorm package tags declares how database migrations must be done:

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

In this example for the field Email with gorm tag we declare that corresponding column in database for the field email must be of type varchar and 100 maximum length and it also must have unique index.

other example is binding tags that are used very mostly in gin package.

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


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

the binding tag in this example gives hint to gin package that the data sent to API must have user and password fields cause these fields are tagged as required.

So generraly tags are data that packages require to know how should they treat with data of type different structs and best way to get familiar with the tags a package needs is READING A PACKAGE DOCUMENTATION COMPLETELY.

答案4

得分: 0

在Go语言中,标签(tags)实际上是与结构体字段关联的元数据。它们被定义为出现在结构体定义中字段名后面的字符串字面量,并用反引号或双引号括起来。

标签在Go语言中有几个用途:

  1. 序列化和反序列化:标签最常见的用途之一是帮助进行数据的序列化和反序列化。例如,在将结构体编码为JSON时,编码器将使用标签值来确定用于每个字段的JSON键名。
  2. 反射:Go语言的反射包允许程序在运行时检查和修改程序的结构。标签可以用于提供关于结构体字段的附加信息,这些信息可以通过反射访问。
  3. 验证:标签可以用于验证存储在结构体中的数据。例如,一个标签可以指示特定字段必须是有效的电子邮件地址,或者必须是非负整数。
  4. 文档:标签可以用于提供关于结构体字段的附加文档。这对于像godoc这样的工具非常有用,它可以根据源代码注释和其他元数据为Go程序生成文档。
英文:

In Go, tags are essentially metadata associated with a struct field. They are defined as string literals that appear after the field name in a struct definition, and are enclosed in backticks or double quotes.

Tags serve several purposes in Go:

  1. Serialization and Deserialization: One of the most common uses of tags is to aid in the serialization and deserialization of data. For example, when encoding a struct as JSON, the encoder will use the tag values to determine the JSON key names to use for each field.
  2. Reflection: Go's reflection package allows programs to inspect and modify the structure of a program at runtime. Tags can be used to provide additional information about the fields of a struct that can be accessed via reflection.
  3. Validation: Tags can be used to validate the data stored in a struct. For example, a tag could indicate that a particular field must be a valid email address, or that it must be a non-negative integer.
  4. Documentation: Tags can be used to provide additional documentation about the fields of a struct. This can be useful for tools like godoc, which generate documentation for Go programs based on source code comments and other metadata.

huangapple
  • 本文由 发表于 2012年6月2日 08:10:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/10858787.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定