À quoi servent les balises dans Go?

393

Dans la spécification de langue Go , il mentionne un bref aperçu des balises:

Une déclaration de champ peut être suivie d'une balise littérale de chaîne facultative, qui devient un attribut pour tous les champs de la déclaration de champ correspondante. Les balises sont rendues visibles à travers une interface de réflexion mais sont sinon ignorées.

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

Ceci est une très courte explication OMI, et je me demandais si quelqu'un pourrait me fournir à quoi serviraient ces balises?

liamzebedee
la source
J'ai une question connexe concernant les utilisations des commentaires «sémantiques»: stackoverflow.com/questions/53101458/…
Bruce Adams
Correction de ce lien devrait être stackoverflow.com/q/53487371/1569204
Bruce Adams

Réponses:

642

Une balise pour un champ vous permet d'attacher des méta-informations au champ qui peuvent être acquises par réflexion. Habituellement, il est utilisé pour fournir des informations de transformation sur la façon dont un champ struct est codé ou décodé à partir d'un autre format (ou stocké / récupéré à partir d'une base de données), mais vous pouvez l'utiliser pour stocker les méta-informations que vous souhaitez, destinées à un autre paquet ou pour votre propre usage.

Comme mentionné dans la documentation de reflect.StructTag, par convention, la valeur d'une chaîne de balises est une liste de key:"value"paires séparées par des espaces , par exemple:

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

le key désigne généralement le package pour lequel le suivant "value"est destiné, par exemple les jsonclés sont traitées / utilisées par le encoding/jsonpackage.

Si plusieurs informations doivent être transmises dans le "value", elles sont généralement spécifiées en les séparant par une virgule ( ','), par exemple

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

Habituellement, une valeur de tiret ( '-') pour les "value"moyens d'exclure le champ du processus (par exemple, dans le cas où jsoncela signifie de ne pas marshaler ou démarsaler ce champ).

Exemple d'accès à vos balises personnalisées à l'aide de la réflexion

Nous pouvons utiliser la réflexion ( reflectpackage) pour accéder aux valeurs des balises des champs struct. Fondamentalement, nous devons acquérir le Typede notre structure, puis nous pouvons interroger des champs, par exemple avec Type.Field(i int)ou Type.FieldByName(name string). Ces méthodes renvoient une valeur StructFieldqui décrit / représente un champ struct; et StructField.Tagest une valeur de type StructTagqui décrit / représente une valeur de balise.

Auparavant, nous parlions de «convention» . Ce moyen de la convention que si vous le suivez, vous pouvez utiliser la StructTag.Get(key string)méthode qui analyse la valeur d'une variable et vous renvoie le "value"de keyvous spécifiez. La convention est implémentée / intégrée dans cette Get()méthode. Si vous ne respectez pas la convention, vous Get()ne pourrez pas analyser les key:"value"paires et trouver ce que vous cherchez. Ce n'est pas non plus un problème, mais vous devez ensuite implémenter votre propre logique d'analyse.

Il y a aussi StructTag.Lookup()(a été ajouté dans Go 1.7) qui est "similaire Get()mais distingue la balise ne contenant pas la clé donnée de la balise associant une chaîne vide à la clé donnée" .

Voyons donc un exemple 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"))
}

Sortie (essayez-la sur le 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 avait une présentation sur les balises struct appelée:

Les nombreux visages des balises Struct (diapositive) (et une vidéo )

Voici une liste des clés de balise couramment utilisées:

icza
la source
29
Excellente réponse. Beaucoup plus d'informations utiles ici que dans celle avec dix fois ce karma.
Dark Egregious du
3
très joli résumé!
stevenferrer
3
Quelle réponse impressionnante
Alberto Megía
2
Très bonne réponse! Je vous remercie!
JumpAlways
2
Réponse étonnante, merci pour toutes ces informations!
Sam Holmes
158

Voici un exemple très simple de balises utilisées avec le encoding/json package pour contrôler la façon dont les champs sont interprétés pendant l'encodage et le décodage:

Essayez en direct: 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"}

Le package json peut regarder les balises du champ et savoir comment mapper le champ struct json <=>, ainsi que des options supplémentaires comme s'il doit ignorer les champs vides lors de la sérialisation vers json.

Fondamentalement, tout package peut utiliser la réflexion sur les champs pour examiner les valeurs des balises et agir sur ces valeurs. Il y a un peu plus d'informations à leur sujet dans le package Reflect
http://golang.org/pkg/reflect/#StructTag :

Par convention, les chaînes de balises sont une concaténation de paires de clés éventuellement séparées par des espaces: "valeur". Chaque clé est une chaîne non vide composée de caractères non contrôlés autres que l'espace (U + 0020 ''), la citation (U + 0022 '"') et les deux points (U + 003A ':'). Chaque valeur est citée en utilisant les caractères U + 0022 '"' et la syntaxe littérale de la chaîne Go.

jdi
la source
7
Un peu comme des annotations Java?
Ismail Badawi
8
@isbadawi: Je ne suis pas un gars java, mais en un coup d'œil sur la définition des annotations java, oui, il semble qu'ils atteignent le même objectif; attacher des métadonnées à des éléments pouvant être examinés lors de l'exécution.
jdi
16
Pas vraiment des annotations java. Les annotations Java sont sécurisées et vérifiées au moment de la compilation - et non les littéraux de chaîne comme go. Les annotations Java sont beaucoup plus puissantes et robustes que les dispositions de base des métadonnées golang.
sam
3
Dans le cadre du pilote MongoDB pour Go, mgo utilise également des balises dans son package bson (qui peut également être utilisé par lui-même). Il vous donne un contrôle précis sur ce que BSON est généré. Voir godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno
2
Y a-t-il d'autres exemples que JSON et BSON?
Max Heiber
2

C'est une sorte de spécifications qui spécifie comment les packages sont traités avec un champ balisé.

par exemple:

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

La balise json informe le jsonpackage que la sortie a été gérée par l'utilisateur suivant

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

serait comme ça:

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

un autre exemple est gormles balises de package déclarent comment les migrations de base de données doivent être effectuées:

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
}

Dans cet exemple pour le champ Emailavec la balise gorm, nous déclarons que la colonne correspondante dans la base de données pour le champ e-mail doit être de type varchar et 100 de longueur maximale et doit également avoir un index unique.

un autre exemple est les bindingbalises qui sont utilisées très majoritairement dans les ginpackages.

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 balise de liaison dans cet exemple indique au package gin que les données envoyées à l'API doivent avoir des champs d'utilisateur et de mot de passe car ces champs sont balisés comme requis.

Les balises générales sont donc des données dont les packages ont besoin pour savoir comment traiter les données avec des structures de type différent et la meilleure façon de se familiariser avec les balises dont un package a besoin est de LIRE UNE DOCUMENTATION COMPLÈTE COMPLÈTEMENT.

Milad Khodabandehloo
la source