¿Cuáles son los usos para las etiquetas en Go?

392

En la especificación de idioma de Go , menciona una breve descripción general de las etiquetas:

Una declaración de campo puede ir seguida de una etiqueta literal de cadena opcional, que se convierte en un atributo para todos los campos en la declaración de campo correspondiente. Las etiquetas se hacen visibles a través de una interfaz de reflexión, pero de lo contrario se ignoran.

// 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 es una explicación muy breve de la OMI, y me preguntaba si alguien podría proporcionarme con qué uso serían estas etiquetas.

liamzebedee
fuente
Tengo una pregunta relacionada con los usos de los comentarios 'semánticos': stackoverflow.com/questions/53101458/…
Bruce Adams
Corrección de que el enlace debe ser stackoverflow.com/q/53487371/1569204
Bruce Adams

Respuestas:

641

Una etiqueta para un campo le permite adjuntar metainformación al campo que se puede adquirir mediante la reflexión. Por lo general, se usa para proporcionar información de transformación sobre cómo se codifica o descodifica un campo de estructura desde otro formato (o se almacena / recupera de una base de datos), pero puede usarlo para almacenar cualquier metainformación que desee, ya sea para otro paquete o para su propio uso.

Como se menciona en la documentación de reflect.StructTag, por convención, el valor de una cadena de etiqueta es una lista de key:"value"pares separados por espacios , por ejemplo:

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

Por lo keygeneral, denota el paquete para el que "value"es posterior , por ejemplo, las jsonclaves son procesadas / utilizadas por el encoding/jsonpaquete.

Si se va a pasar información múltiple en "value", generalmente se especifica separándola con una coma ( ','), p. Ej.

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

Por lo general, un valor de guión ( '-') para los "value"medios para excluir el campo del proceso (por ejemplo, en el caso de jsonque signifique no ordenar o desarmar ese campo).

Ejemplo de acceso a sus etiquetas personalizadas mediante reflexión

Podemos usar la reflexión ( reflectpaquete) para acceder a los valores de etiqueta de los campos de estructura. Básicamente, necesitamos adquirir el Typede nuestra estructura, y luego podemos consultar los campos, por ejemplo, con Type.Field(i int)o Type.FieldByName(name string). Estos métodos devuelven un valor StructFieldque describe / representa un campo de estructura; y StructField.Tages un valor de tipo StructTagque describe / representa un valor de etiqueta.

Anteriormente hablamos de "convención" . Esta convención significa que si la sigue, puede usar el StructTag.Get(key string)método que analiza el valor de una etiqueta y le devuelve "value"la keyque especifique. La convención se implementa / integra en este Get()método. Si no sigue la convención, Get()no podrá analizar key:"value"pares y encontrar lo que está buscando. Eso tampoco es un problema, pero luego debe implementar su propia lógica de análisis.

También hay StructTag.Lookup()(se agregó en Go 1.7) que es "como Get()pero distingue la etiqueta que no contiene la clave dada de la etiqueta que asocia una cadena vacía con la clave dada" .

Entonces veamos un ejemplo simple:

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

Salida (pruébalo en 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 tuvo una presentación sobre etiquetas de estructura llamada:

Las muchas caras de las etiquetas de estructura (diapositiva) (y un video )

Aquí hay una lista de claves de etiqueta comúnmente utilizadas:

icza
fuente
28
Excelente respuesta Mucho más información útil aquí que en la que tiene diez veces este karma.
Darth Egregious
2
muy buen resumen!
stevenferrer
2
Qué respuesta tan increíble
Alberto Megía
1
¡Gran respuesta! ¡Gracias!
JumpAlways
1
Increíble respuesta, ¡gracias por toda esta información!
Sam Holmes
157

Aquí hay un ejemplo realmente simple de etiquetas que se usan con el encoding/jsonpaquete para controlar cómo se interpretan los campos durante la codificación y decodificación:

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

El paquete json puede mirar las etiquetas para el campo y se le puede decir cómo mapear el campo de estructura json <=>, y también opciones adicionales como si debe ignorar los campos vacíos cuando se serializa de nuevo a json.

Básicamente, cualquier paquete puede usar la reflexión en los campos para mirar los valores de las etiquetas y actuar sobre esos valores. Hay un poco más de información sobre ellos en el paquete
reflect http://golang.org/pkg/reflect/#StructTag :

Por convención, las cadenas de etiquetas son una concatenación de claves opcionalmente separadas por espacios: pares de "valores". Cada clave es una cadena no vacía que consta de caracteres que no son de control distintos del espacio (U + 0020 ''), comillas (U + 0022 '"') y dos puntos (U + 003A ':'). Cada valor se cita usando los caracteres U "0022 '"' y la sintaxis literal de la cadena Go.

jdi
fuente
66
¿Algo parecido a las anotaciones de Java?
Ismail Badawi
77
@isbadawi: no soy un chico de Java, pero a simple vista de la definición de las anotaciones de Java, sí, parece que están logrando el mismo objetivo; adjuntando metadatos a elementos que pueden ser examinados en tiempo de ejecución.
jdi
15
No son realmente anotaciones de Java. Las anotaciones Java son de tipo seguro y se comprueba el tiempo de compilación, no literales de cadena como go. Las anotaciones de Java son mucho más potentes y robustas que las disposiciones básicas de metadatos de Golang.
sentó
2
Como parte del controlador MongoDB para Go, mgo también usa etiquetas en su paquete bson (que también se puede usar solo). Le da un control preciso sobre qué BSON se genera. Ver godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno
1
¿Hay otros ejemplos además de JSON y BSON?
Max Heiber
1

Es una especie de especificaciones que especifica cómo se tratan los paquetes con un campo que está etiquetado.

por ejemplo:

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

La etiqueta json informa al jsonpaquete que marcó la salida del siguiente usuario

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

sería así:

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

otro ejemplo es gormque las etiquetas de paquete declaran cómo deben realizarse las migraciones de la base de datos:

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
}

En este ejemplo para el campo Emailcon etiqueta gorm declaramos que la columna correspondiente en la base de datos para el correo electrónico del campo debe ser de tipo varchar y 100 de longitud máxima y también debe tener un índice único.

otro ejemplo son las bindingetiquetas que se usan principalmente en ginpaquetes.

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
}

la etiqueta de enlace en este ejemplo da una pista al paquete gin de que los datos enviados a la API deben tener campos de usuario y contraseña porque estos campos están etiquetados según sea necesario.

Por lo general, las etiquetas son datos que los paquetes requieren para saber cómo deben tratarse con datos de diferentes tipos de estructuras y la mejor manera de familiarizarse con las etiquetas que necesita un paquete es LEER COMPLETAMENTE UNA DOCUMENTACIÓN DE PAQUETE.

Milad Khodabandehloo
fuente