Які використання тегів у програмі Go?


392

У специфікації мови 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, і мені було цікаво, чи хтось міг би надати мені те, чим би користувалися ці теги?


У мене пов'язане запитання щодо використання "семантичних" коментарів: stackoverflow.com/questions/53101458/…
Брюс Адамс

Виправлення цього посилання має бути stackoverflow.com/q/53487371/1569204
Брюс Адамс

Відповіді:


641

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

Як зазначається в документації 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 ):

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 відбулася презентація про теги Stru:

Теги безлічі облич структури (слайд) відео )

Ось перелік часто використовуваних тегів:


28
Відмінна відповідь. Тут набагато корисніша інформація, ніж у тій, що має десятку разів цю карму.
Дарт Егрегійний

2
дуже приємне резюме!
stevenferrer

2
Яка дивовижна відповідь
Альберто Мегія

1
Чудова відповідь! Дякую!
JumpAlways

1
Дивовижна відповідь, дякую за всю цю інформацію!
Сем Холмс

157

Ось дійсно простий приклад тегів, які використовуються в 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.


6
Наче подібні анотації на Java?
Ісмаїл Бадаві

7
@isbadawi: Я не хлопець java, але при швидкому погляді на визначення анотацій Java, так, здається, вони досягають тієї самої мети; приєднання метаданих до елементів, які можна перевірити під час виконання.
jdi

15
Не дуже явові анотації. Анотації на Java є безпечними для типу та перевіряють час компіляції - не рядкові літерали, як go. Анотації на Java набагато потужніші та надійніші, ніж основні положення голоданг про метадані.
сів

2
Як частина драйвера MongoDB для Go, mgo також використовує теги у своєму пакеті bson (який також може використовуватися сам). Це дає точний контроль над тим, що генерується BSON. Дивіться godoc.org/labix.org/v2/mgo/bson#pkg-files
Ено

1
Чи є інші приклади, окрім JSON та BSON?
Макс Хайбер

1

Це якась специфікація, яка визначає, як пакети поводяться з полем, яке позначено тегами.

наприклад:

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

Тег json повідомляє 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
}

прив'язуючий тег у цьому прикладі дає підказку пакету gin про те, що дані, надіслані API, повинні мати поля користувача та пароля, оскільки ці поля позначаються як потрібно.

Отже, загальні теги - це дані, від яких пакунки вимагають знати, як слід поводитися з даними різних структур та найкращим способом ознайомитись з тегами, які потрібен пакет - це ПОЧАТКОВА ДОКУМЕНТАЦІЯ ПАКЕТИ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.