Jakie są zastosowania tagów w Go?

392

W specyfikacji języka Go wspomina krótki przegląd tagów:

Po deklaracji pola może następować opcjonalny znacznik literału łańcucha, który staje się atrybutem dla wszystkich pól w odpowiedniej deklaracji pola. Tagi są widoczne przez interfejs odbicia, ale w przeciwnym razie są ignorowane.

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

To jest bardzo krótkie wyjaśnienie IMO i zastanawiałem się, czy ktokolwiek mógłby mi zapewnić, jaki byłby użytek z tych tagów?

liamzebedee
źródło
Mam powiązane pytanie dotyczące stosowania komentarzy „semantycznych”: stackoverflow.com/questions/53101458/...
Bruce Adams,
Korekta tego linku powinna być stosoverflow.com/q/53487371/1569204
Bruce Adams,

Odpowiedzi:

641

Znacznik pola umożliwia dołączenie do pola meta-informacji, które można uzyskać za pomocą odbicia. Zwykle służy do dostarczania informacji o transformacji, w jaki sposób pole struct jest kodowane lub dekodowane z innego formatu (lub przechowywane / pobierane z bazy danych), ale możesz go użyć do przechowywania dowolnych meta-informacji, które chcesz, albo przeznaczonych dla innego pakiet lub na własny użytek.

Jak wspomniano w dokumentacji reflect.StructTag, zgodnie z konwencją wartość ciągu znacznika jest oddzieloną spacją listą key:"value"par, na przykład:

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

keyZwykle oznacza pakietu, który następnie "value"jest, na przykład, jsonklucze są przetwarzane / wykorzystywane przezencoding/json pakiet.

Jeśli ma zostać przekazanych wiele informacji "value", zwykle określa się je, oddzielając je przecinkiem ( ','), np

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

Zwykle wartość myślnika ( '-') dla "value"sposobu wykluczenia pola z procesu (np. W przypadkujson gdy oznacza to nie marszrowanie lub odmarszowanie tego pola).

Przykład uzyskiwania dostępu do niestandardowych tagów za pomocą odbicia

Możemy użyć refleksji ( reflectpakietu), aby uzyskać dostęp do wartości znaczników pól struct. Zasadniczo musimy zdobyć Typenaszą strukturę, a następnie możemy wyszukiwać pola np. Za pomocą Type.Field(i int)lub Type.FieldByName(name string). Te metody zwracają wartość, StructFieldktóra opisuje / reprezentuje pole struct; i StructField.Tagjest wartością typuStructTag która opisuje / reprezentuje wartość znacznika.

Wcześniej rozmawialiśmy o „konwencji” . Ta konwencja oznacza, że ​​jeśli będziesz go przestrzegać, możesz użyć StructTag.Get(key string)metody, która analizuje wartość znacznika i zwraca ci określoną "value"przez keyCiebie wartość. Konwencja jest realizowany / wbudowana w tej Get()metodzie. Jeśli nie zastosujesz się do konwencji, Get()nie będziesz mógł przeanalizowaćkey:"value" par i znaleźć tego, czego szukasz. To też nie jest problem, ale musisz zaimplementować własną logikę parsowania.

Jest też StructTag.Lookup()(został dodany w wersji 1.7), który jest „podobny, Get()ale odróżnia znacznik niezawierający danego klucza od znacznika kojarzącego pusty ciąg z danym kluczem” .

Zobaczmy więc prosty przykład:

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

Wyjście (wypróbuj na 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 miał prezentację na temat tagów struktur o nazwie:

The Many Faces of Struct Tags (slajd) (i wideo )

Oto lista najczęściej używanych kluczy tagów:

icza
źródło
28
Doskonała odpowiedź. O wiele bardziej przydatne informacje tutaj niż w tej z dziesięciokrotnie większą karmą.
Darth Egregious
2
bardzo miłe podsumowanie!
stevenferrer
2
Cóż za niesamowita odpowiedź
Alberto Megía
1
Świetna odpowiedź! Dziękuję Ci!
JumpAlways
1
Niesamowita odpowiedź, dziękuję za wszystkie te informacje!
Sam Holmes,
157

Oto naprawdę prosty przykład znaczników używanych z encoding/jsonpakietem do kontrolowania interpretacji pól podczas kodowania i dekodowania:

Wypróbuj na żywo: 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"}

Pakiet json może spojrzeć na znaczniki pola i dowiedzieć się, jak zamapować pole struktury json <=>, a także dodatkowe opcje, takie jak to, czy powinien ignorować puste pola podczas serializacji z powrotem do json.

Zasadniczo każdy pakiet może używać refleksji nad polami, aby patrzeć na wartości znaczników i działać na te wartości. Jest trochę więcej informacji na ich temat w pakiecie odzwierciedlającym
http://golang.org/pkg/reflect/#StructTag :

Zgodnie z konwencją ciągi znaczników są konkatenacją opcjonalnie rozdzielonych spacjami par klucz: wartość. Każdy klucz jest niepustym ciągiem znaków składającym się z niekontrolowanych znaków innych niż spacja (U + 0020 ''), cytat (U + 0022 '' ') i dwukropek (U + 003A': '). Każda wartość jest cytowana używając znaków U + 0022 „” i składni literału Go.

jdi
źródło
6
Coś w rodzaju adnotacji Java?
Ismail Badawi
7
@isbadawi: Nie jestem facetem z Javy, ale na pierwszy rzut oka definicja adnotacji w Javie wydaje się, że osiągają ten sam cel; dołączanie metadanych do elementów, które można badać w czasie wykonywania.
jdi
15
Nie tak naprawdę adnotacje Java. Adnotacje Java są bezpieczne pod względem typu i sprawdzane pod kątem czasu kompilacji - nie literałów łańcuchowych takich jak go. Adnotacje Java są znacznie potężniejsze i bardziej niezawodne niż podstawowe zapisy metadanych golang.
sob
2
Jako część sterownika MongoDB dla Go, mgo używa także znaczników w swoim pakiecie bson (z którego można również korzystać samodzielnie). Daje ci precyzyjną kontrolę nad tym, co generuje BSON. Zobacz godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno
1
Czy istnieją inne przykłady oprócz JSON i BSON?
Max Heiber,
1

Jest to rodzaj specyfikacji, która określa, w jaki sposób pakiety traktują pole oznaczone.

na przykład:

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

Tag json informuje jsonpakiet, że zebrał dane wyjściowe kolejnego użytkownika

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

wyglądałoby to tak:

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

innym przykładem są gormznaczniki pakietów deklarujące, w jaki sposób należy przeprowadzić migrację bazy danych:

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
}

W tym przykładzie dla pola Emailz tagiem gorm deklarujemy, że odpowiednia kolumna w bazie danych dla adresu e-mail pola musi być typu varchar i mieć maksymalną długość 100, a także musi mieć unikalny indeks.

innym przykładem są bindingtagi, które są używane głównie w ginpakiecie.

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
}

tag wiązania w tym przykładzie daje wskazówkę do pakietu gin, że dane wysyłane do API muszą zawierać pola użytkownika i hasła, aby pola te były odpowiednio oznaczone.

Tak więc tagi generalne to dane, których pakiety potrzebują, aby wiedzieć, jak powinny traktować dane różnego typu, a najlepszym sposobem na zaznajomienie się z tagami, których potrzebuje pakiet, jest PEŁNE ODCZYT DOKUMENTACJI PAKIETU.

Milad Khodabandehloo
źródło