Was sind die Verwendung (en) für Tags in Go?

392

In der Go-Sprachspezifikation wird eine kurze Übersicht über Tags erwähnt:

Auf eine Felddeklaration kann ein optionales String-Literal-Tag folgen, das zu einem Attribut für alle Felder in der entsprechenden Felddeklaration wird. Die Tags werden über eine Reflektionsschnittstelle sichtbar gemacht, ansonsten jedoch ignoriert.

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

Dies ist eine sehr kurze Erklärung, IMO, und ich habe mich gefragt, ob mir jemand mitteilen könnte, welchen Nutzen diese Tags haben würden.

Liamzebedee
quelle
Ich habe eine verwandte Frage zur Verwendung von 'semantischen' Kommentaren: stackoverflow.com/questions/53101458/…
Bruce Adams
Korrektur, dass der Link stackoverflow.com/q/53487371/1569204
Bruce Adams

Antworten:

641

Mit einem Tag für ein Feld können Sie dem Feld Metainformationen hinzufügen, die mithilfe von Reflektion erfasst werden können. Normalerweise wird es verwendet, um Transformationsinformationen darüber bereitzustellen, wie ein Strukturfeld in ein anderes Format codiert oder aus diesem dekodiert (oder aus einer Datenbank gespeichert / abgerufen) wird. Sie können es jedoch verwenden, um die gewünschten Metainformationen zu speichern, die entweder für ein anderes bestimmt sind Paket oder für den eigenen Gebrauch.

Wie in der Dokumentation von erwähnt reflect.StructTag, ist der Wert einer Tag-Zeichenfolge gemäß Konvention eine durch Leerzeichen getrennte Liste von key:"value"Paaren, zum Beispiel:

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

Das keybezeichnet normalerweise das Paket, "value"für das das nachfolgende bestimmt ist, zum Beispiel werden jsonSchlüssel vom encoding/jsonPaket verarbeitet / verwendet .

Wenn mehrere Informationen in der übergeben werden sollen "value", wird dies normalerweise durch Trennen durch ein Komma ( ',') angegeben, z

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

Normalerweise bedeutet ein Strichwert ( '-') für das "value"Mittel, um das Feld vom Prozess auszuschließen (z. B. wenn jsondies bedeutet, dass dieses Feld nicht gemarshallt oder unmarshallt wird).

Beispiel für den Zugriff auf Ihre benutzerdefinierten Tags mithilfe von Reflection

Wir können Reflection ( reflectPaket) verwenden, um auf die Tag-Werte von Strukturfeldern zuzugreifen. Grundsätzlich müssen wir die Typeunserer Struktur erwerben , und dann können wir Felder abfragen, z . B. mit Type.Field(i int)oder Type.FieldByName(name string). Diese Methoden geben einen Wert zurück, der StructFieldein Strukturfeld beschreibt / darstellt. und StructField.Tagist ein Wert vom Typ, StructTagder einen Tag-Wert beschreibt / darstellt.

Zuvor haben wir über "Konvention" gesprochen . Diese Konvention bedeutet, dass Sie, wenn Sie sie befolgen, die StructTag.Get(key string)Methode verwenden können, die den Wert eines Tags analysiert und Ihnen den "value"von keyIhnen angegebenen Wert zurückgibt . Die Konvention ist in diese Get()Methode implementiert / eingebaut . Wenn Sie die Konvention nicht befolgen, Get()können Sie keine key:"value"Paare analysieren und finden, wonach Sie suchen. Das ist auch kein Problem, aber dann müssen Sie Ihre eigene Parsing-Logik implementieren.

Es gibt auch StructTag.Lookup()(wurde in Go 1.7 hinzugefügt), das "ähnlich ist, Get()aber das Tag, das den angegebenen Schlüssel nicht enthält, von dem Tag unterscheidet, das dem angegebenen Schlüssel eine leere Zeichenfolge zuordnet" .

Schauen wir uns also ein einfaches Beispiel an:

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

Ausgabe (versuchen Sie es auf dem Go-Spielplatz ):

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 hatte eine Präsentation über Struktur-Tags mit dem Namen:

Die vielen Gesichter von Struktur-Tags (Folie) (und ein Video )

Hier ist eine Liste häufig verwendeter Tag-Schlüssel:

icza
quelle
28
Hervorragende Antwort. Viel nützlichere Informationen hier als in der mit dem zehnfachen Karma.
Darth Egregious
2
sehr schöne Zusammenfassung!
Stevenferrer
2
Was für eine großartige Antwort
Alberto Megía
1
Gute Antwort! Vielen Dank!
JumpAlways
1
Erstaunliche Antwort, danke für all diese Informationen!
Sam Holmes
157

Hier ist ein wirklich einfaches Beispiel für Tags, die mit dem encoding/jsonPaket verwendet werden, um zu steuern, wie Felder während des Codierens und Decodierens interpretiert werden:

Versuchen Sie es live: 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"}

Das json-Paket kann sich die Tags für das Feld ansehen und erfahren, wie das json <=> -Strukturfeld zugeordnet wird, sowie zusätzliche Optionen, z. B. ob leere Felder bei der Serialisierung zurück zu json ignoriert werden sollen.

Grundsätzlich kann jedes Paket die Felder reflektieren, um Tag-Werte anzuzeigen und auf diese Werte zu reagieren. Weitere Informationen dazu finden Sie im Reflect-Paket
http://golang.org/pkg/reflect/#StructTag :

Konventionell sind Tag-Strings eine Verkettung von optional durch Leerzeichen getrennten Schlüssel: "Wert" -Paaren. Jeder Schlüssel ist eine nicht leere Zeichenfolge, die aus anderen Nichtsteuerzeichen als Leerzeichen (U + 0020 ''), Anführungszeichen (U + 0022 '"') und Doppelpunkt (U + 003A ':') besteht. Jeder Wert wird in Anführungszeichen gesetzt Verwenden von U + 0022 '"' Zeichen und Go-String-Literal-Syntax.

jdi
quelle
6
Ein bisschen wie Java-Annotationen?
Ismail Badawi
7
@isbadawi: Ich bin kein Java-Typ, aber auf einen Blick auf die Definition von Java-Annotationen scheint es, dass sie das gleiche Ziel erreichen. Anhängen von Metadaten an Elemente, die zur Laufzeit untersucht werden können.
JDI
15
Nicht wirklich Java-Anmerkungen. Java-Annotationen sind typsicher und die Kompilierungszeit überprüft - keine String-Literale wie go. Java-Annotationen sind viel leistungsfähiger und robuster als grundlegende Metadatenbestimmungen für Golang.
saß
2
Als Teil des MongoDB-Treibers für Go verwendet mgo auch Tags in seinem bson-Paket (das auch von sich selbst verwendet werden kann). Sie können genau steuern, was BSON generiert wird. Siehe godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno
1
Gibt es neben JSON und BSON noch andere Beispiele?
Max Heiber
1

Es handelt sich um eine Art von Spezifikation, die angibt, wie Pakete mit einem mit Tags versehenen Feld behandelt werden.

zum Beispiel:

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

Das json-Tag informiert das jsonPaket darüber, dass die Ausgabe des folgenden Benutzers gemarshallt wurde

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

wäre so:

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

Ein anderes Beispiel ist, dass gormPaket-Tags deklarieren, wie Datenbankmigrationen durchgeführt werden müssen:

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
}

In diesem Beispiel für das Feld Emailmit dem Gorm-Tag deklarieren wir, dass die entsprechende Spalte in der Datenbank für die Feld-E-Mail vom Typ varchar und maximal 100 sein muss und außerdem einen eindeutigen Index haben muss.

Ein anderes Beispiel sind bindingTags, die meistens im ginPaket verwendet werden.

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
}

Das Bindungs-Tag in diesem Beispiel gibt dem Gin-Paket einen Hinweis darauf, dass die an die API gesendeten Daten Benutzer- und Kennwortfelder enthalten müssen, da diese Felder nach Bedarf markiert werden.

Generell sind Tags Daten, die Pakete benötigen, um zu wissen, wie sie mit Daten unterschiedlicher Strukturen umgehen sollen. Der beste Weg, sich mit den Tags vertraut zu machen, die ein Paket benötigt, ist das vollständige Lesen einer Paketdokumentation.

Milad Khodabandehloo
quelle