Как использовать теги в Go?

393

В спецификации языка Go упоминается краткий обзор тегов:

За объявлением поля может следовать необязательный строковый литерал, который становится атрибутом для всех полей в соответствующем объявлении поля. Теги становятся видимыми через интерфейс отражения, но в остальном игнорируются.

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

Это очень короткое объяснение IMO, и мне было интересно, если кто-нибудь может предоставить мне, как будут использоваться эти теги?

liamzebedee
источник
У меня есть связанный вопрос для использования «семантических» комментариев: stackoverflow.com/questions/53101458/…
Брюс Адамс
Исправьте, что ссылка должна быть stackoverflow.com/q/53487371/1569204
Брюс Адамс

Ответы:

642

Тег для поля позволяет вам прикрепить метаинформацию к полю, которую можно получить с помощью отражения. Обычно он используется для предоставления информации о преобразовании того, как поле структуры кодируется или декодируется из другого формата (или сохраняется / извлекается из базы данных), но вы можете использовать его для хранения любой мета-информации, которую вы хотите, либо предназначенной для другого пакет или для собственного использования.

Как упомянуто в документации reflect.StructTag, условно значение строки тега представляет собой разделенный пробелами список key:"value"пар, например:

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

keyОбычно обозначает пакет , что последующий "value"для, например , jsonключи обрабатываются / используются encoding/jsonпакета.

Если несколько данных должны быть переданы в "value", обычно это указывается разделением запятой ( ','), например

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

Обычно значение тире ( '-') "value"означает, что поле исключено из процесса (например, если jsonоно означает не маршалировать и не демаршировать это поле).

Пример доступа к вашим пользовательским тегам с помощью отражения

Мы можем использовать отражение ( reflectпакет) для доступа к значениям тегов структурных полей. По сути, нам нужно получить Typeструктуру нашей структуры, а затем мы можем запрашивать поля, например, с помощью Type.Field(i int)или Type.FieldByName(name string). Эти методы возвращают значение, StructFieldкоторое описывает / представляет поле структуры; и StructField.Tagявляется значением типа, StructTagкоторое описывает / представляет значение тега.

Ранее мы говорили о «конвенции» . Это соглашение означает, что если вы следуете ему, вы можете использовать StructTag.Get(key string)метод, который анализирует значение тега и возвращает вам значение, "value"указанное keyвами. Конвенции реализованы / встроенная в этот Get()метод. Если вы не будете следовать соглашению, Get()не сможете разобрать key:"value"пары и найти то, что ищете. Это тоже не проблема, но тогда вам нужно реализовать собственную логику синтаксического анализа.

Также есть StructTag.Lookup()(был добавлен в Go 1.7), который «похож, Get()но отличает тег, не содержащий данный ключ, от тега, связывающего пустую строку с данным ключом» .

Итак, давайте посмотрим на простой пример:

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

u := User{"Bob", "bob@mycompany.com"}
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"))
}

Вывод (попробуйте на 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 провел презентацию о структурных тегах под названием:

Теги Многогранности Структуры (слайд) видео )

Вот список часто используемых ключей тегов:

icza
источник
29
Отличный ответ. Здесь гораздо больше полезной информации, чем в той, которая в десять раз превышает эту карму.
Дарт Егрегиус
3
очень хорошее резюме!
Стивенферрер
3
Какой удивительный ответ
Альберто Мегия
2
Отличный ответ! Спасибо!
JumpAlways
2
Удивительный ответ, спасибо за всю эту информацию!
Сэм Холмс
158

Вот очень простой пример использования тегов с encoding/jsonпакетом для управления интерпретацией полей во время кодирования и декодирования:

Попробуйте вживую: 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"}

Пакет json может просмотреть теги для поля и узнать, как отобразить поле структуры json <=>, а также дополнительные параметры, например, следует ли игнорировать пустые поля при сериализации обратно в json.

По сути, любой пакет может использовать отражение в полях для просмотра значений тегов и воздействия на эти значения. Немного больше информации о них в пакете
рефлекса http://golang.org/pkg/reflect/#StructTag :

По соглашению строки тегов представляют собой конкатенацию необязательно разделенных пробелами пар «значение». Каждый ключ является непустой строкой, состоящей из неуправляемых символов, кроме пробела (U + 0020 ''), кавычки (U + 0022 '"') и двоеточия (U + 003A ':'). Каждое значение заключено в кавычки используя символы "+" U + 0022 и буквенный синтаксис строки Go.

JDI
источник
7
Вроде как аннотации Java?
Исмаил Бадави
8
@isbadawi: я не Java-парень, но с первого взгляда на определение java-аннотаций, да, похоже, они достигают одной и той же цели; прикрепление метаданных к элементам, которые могут быть проверены во время выполнения.
JDI
16
Не совсем Java-аннотации. Java-аннотации безопасны по типу и проверяются временем компиляции, а не строковыми литералами, такими как go. Java-аннотации намного мощнее и надежнее, чем базовые положения о метаданных golang.
сидел
3
Как часть драйвера MongoDB для Go, mgo также использует теги в своем пакете bson (который также может использоваться сам по себе). Это дает вам точный контроль над тем, что генерируется BSON. См. Godoc.org/labix.org/v2/mgo/bson#pkg-files
Ино
2
Есть ли другие примеры, кроме JSON и BSON?
Макс Хейбер
2

Это своего рода спецификации, которые определяют, как пакеты обрабатывают поле с тегами.

например:

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

json tag информирует jsonпакет, который маршаллировал вывод следующего пользователя

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

будет так:

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

Другой пример - gormтеги пакетов, объявляющие, как должны выполняться миграции базы данных:

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
}

В этом примере для поля Emailс тегом gorm мы объявляем, что соответствующий столбец в базе данных для поля электронной почты должен иметь тип varchar и максимальную длину 100, а также иметь уникальный индекс.

Другой пример - bindingтеги, которые используются в основном в ginпакете.

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
}

тег привязки в этом примере дает подсказку пакету джина о том, что данные, отправляемые в API, должны иметь поля пользователя и пароля, поскольку эти поля помечены как обязательные.

Таким образом, общие теги - это данные, которые пакетам необходимо знать, как они должны обращаться с данными различных структур типа, и лучший способ ознакомиться с тегами, которые нужны пакету, - это ЧИТАТЬ ДОКУМЕНТАЦИЮ ПАКЕТА ПОЛНОСТЬЮ.

Милад Ходабандехлоо
источник