Quais são os usos das tags no Go?

392

Na especificação de idioma Go , ele menciona uma breve visão geral das tags:

Uma declaração de campo pode ser seguida por uma tag literal de cadeia opcional, que se torna um atributo para todos os campos na declaração de campo correspondente. As tags são visíveis por meio de uma interface de reflexão, mas são ignoradas.

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

Esta é uma explicação muito curta da IMO, e eu queria saber se alguém poderia me fornecer qual o uso dessas tags.

liamzebedee
fonte
Eu tenho uma pergunta relacionada para fins de comentários 'semântica': stackoverflow.com/questions/53101458/...
Bruce Adams
A correção desse link deve ser stackoverflow.com/q/53487371/1569204
Bruce Adams

Respostas:

641

Uma tag para um campo permite anexar meta-informações ao campo que pode ser adquirido usando reflexão. Geralmente, ele é usado para fornecer informações de transformação sobre como um campo de estrutura é codificado ou decodificado de outro formato (ou armazenado / recuperado de um banco de dados), mas você pode usá-lo para armazenar qualquer meta-informação que desejar, destinada a outra pacote ou para seu próprio uso.

Conforme mencionado na documentação de reflect.StructTag, por convenção, o valor de uma sequência de tags é uma lista de key:"value"pares separados por espaço , por exemplo:

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

O keynormalmente indica o pacote que o subsequente "value"é de, por exemplo, jsonas teclas são processados / utilizado pelo encoding/jsonpacote.

Se várias informações devem ser passadas no "value", normalmente elas são especificadas separando-as com uma vírgula ( ','), por exemplo

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

Geralmente, um valor de hífen ( '-') para os "value"meios de excluir o campo do processo (por exemplo, no caso de jsonisso significar não empacotar ou descompactar esse campo).

Exemplo de acesso às suas tags personalizadas usando reflexão

Podemos usar a reflexão ( reflectpacote) para acessar os valores das tags dos campos struct. Basicamente, precisamos adquirir Typenossa estrutura e, em seguida, podemos consultar os campos, por exemplo, com Type.Field(i int)ou Type.FieldByName(name string). Esses métodos retornam um valor StructFieldque descreve / representa um campo struct; e StructField.Tagé um valor do tipo StructTagque descreve / representa um valor de tag.

Anteriormente, conversamos sobre "convenção" . Essa convenção significa que, se você a seguir, poderá usar o StructTag.Get(key string)método que analisa o valor de uma tag e retorna "value"o que keyvocê especificar. A convenção é implementada / incorporada a este Get()método. Se você não seguir a convenção, Get()não poderá analisar key:"value"pares e encontrar o que está procurando. Isso também não é um problema, mas você precisa implementar sua própria lógica de análise.

Também existe StructTag.Lookup()(foi adicionado no Go 1.7) que é "semelhante, Get()mas distingue, a marca que não contém a chave especificada da marca que associa uma cadeia vazia à chave especificada" .

Então, vamos ver um exemplo simples:

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

u := User{"Bob", "[email protected]"}
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"))
}

Saída (experimente no 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"

O GopherCon 2015 teve uma apresentação sobre tags struct chamada:

As muitas faces das tags Struct (slide) (e um vídeo )

Aqui está uma lista das chaves de tag mais usadas:

icza
fonte
28
Excelente resposta. Informações muito mais úteis aqui do que naquelas com dez vezes esse karma.
Darth Egregious
2
resumo muito bom!
Stevenferrer # 9/17
2
Que resposta incrível
Alberto Megía
11
Ótima resposta! Obrigado!
JumpAlways
11
Resposta incrível, obrigado por todas essas informações!
Sam Holmes
157

Aqui está um exemplo muito simples de tags sendo usadas com o encoding/jsonpacote para controlar como os campos são interpretados durante a codificação e decodificação:

Experimente ao vivo: 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"}

O pacote json pode examinar as tags do campo e saber como mapear o campo json <=> struct, além de opções extras como se deve ignorar os campos vazios ao serializar de volta ao json.

Basicamente, qualquer pacote pode usar a reflexão nos campos para examinar os valores dos tags e agir sobre esses valores. Há um pouco mais de informações sobre eles no pacote de reflexão
http://golang.org/pkg/reflect/#StructTag :

Por convenção, as seqüências de tags são uma concatenação de pares de chave opcionalmente separados por espaço: "valor". Cada chave é uma string não vazia que consiste em caracteres não de controle que não sejam espaço (U + 0020 ''), aspas (U + 0022 '"') e dois pontos (U + 003A ':'). Cada valor é citado usando caracteres U + 0022 '"' e sintaxe literal da string Go.

jdi
fonte
6
Como anotações em Java?
Ismail Badawi
7
@isbadawi: Eu não sou um cara de java, mas, olhando rapidamente para a definição de anotações de java, sim, parece que eles estão alcançando o mesmo objetivo; anexando metadados a elementos que podem ser examinados em tempo de execução.
precisa saber é
15
Não é realmente anotações em java. As anotações Java são do tipo seguro e o tempo de compilação verificado - não literais de cadeias, como go. As anotações Java são muito mais poderosas e robustas do que as provisões de metadados básicos do golang.
sab
2
Como parte do driver MongoDB para Go, o mgo, também usa tags em seu pacote bson (que também pode ser usado por si só). Dá a você controle preciso sobre o que o BSON é gerado. Veja godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno
11
Existem outros exemplos além do JSON e BSON?
Max Heiber
1

É algum tipo de especificação que especifica como os pacotes são tratados com um campo que está marcado.

por exemplo:

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

A tag json informa o jsonpacote que organizou a saída do seguinte usuário

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

seria assim:

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

outro exemplo é as gormtags de pacote que declaram como as migrações do banco de dados devem ser feitas:

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
}

Neste exemplo para o campo Emailcom tag gorm, declaramos que a coluna correspondente no banco de dados para o email do campo deve ser do tipo varchar e 100 de comprimento máximo e também deve ter um índice exclusivo.

outro exemplo são as bindingtags usadas principalmente no ginpacote.

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
}

a tag de ligação neste exemplo dá uma dica ao pacote gin de que os dados enviados à API devem ter campos de usuário e senha, pois esses campos são marcados conforme necessário.

Portanto, tags geralmente são dados que os pacotes precisam saber como devem ser tratados com dados de estruturas diferentes e a melhor maneira de se familiarizar com as tags de que um pacote precisa é LER UMA DOCUMENTAÇÃO DE PACOTE COMPLETAMENTE.

Milad Khodabandehloo
fonte