``
. gofmt
не приводит их к стандартному виду, стоит руководствоваться принятыми в Go-сообществе правилами оформления тегов:snake_case
или camelCase
.type User struct {
ID string `json:"id" format:"uuid"`
Name string `json:"name"`
CreatedAt int64 `json:"created_at" format:"unixtime,seconds"`
}
json
— имя поля JSON-представления объекта (используется JSON-сериализаторами);format
— дополнительные данные о формате поля (могут использоваться другими библиотеками).json
, yaml
, xml
, protobuf
. Как правило, сериализаторы не требуют обязательного указания тегов. Имя поля в результирующем формате данных будет совпадать с именем поля структуры.// Person содержит информацию о пользователе
// и описывает сериализацию в JSON и YAML.
type Person struct {
ID int
Name string `yaml:"name"`
Email string `json:"email" yaml:"email"`
}
nullable
). Есть пакеты, которые используют структурные теги для работы с базами данных: gorm, bun.// User демонстрирует пример описания GORM-модели.
type User struct {
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
Name string `gorm:"size:255"` // указывает размер поля
Email string `gorm:"type:varchar (100);unique_index"`
Phone string `gorm:"index:phone"` // создаёт индекс для этого поля
mark int `gorm:"-"` // не участвует в модели
}
valid
описывает тип поля и допустимые значения.type Example struct {
Name string `valid:"-"`
Email string `valid:"email"`
URL string `valid:"url"`
}
func main() {
// проверяем на валидность email и URL
result, err := govalidator.ValidateStruct(Example{
Email: "johndoe@example.com",
URL: "https://www.ya.ru",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
reflect
стандартной библиотеки Go.reflect
определён type StructTag string
со следующими публичными методами:Lookup(key string) (value string, ok bool)
— возвращает значение тега по имени и флаг присутствия искомого тега в наборе.Get(key string) string
— аналогичен Lookup
, но не возвращает информацию о наличии тега. Если тег не найден, функция вернёт пустую строку.obj
структурного типа и нужно получить теги полей соответствующей структуры:obj := MyType{}
// получаем тип переменной — st имеет тип reflect.Type
st := reflect.TypeOf(obj)
// Type.NumField() — возвращает количество полей
for i:=0; i < st.NumField(); i++ {
// field содержит информацию о поле с i-м индексом (с 0)
field := st.Field(i)
// field.Tag имеет тип StructTag и содержит все теги i-го поля
// выводим имя поля и значение тега `json`
fmt.Println(field.Name, field.Tag.Get("json"))
}
(*Type) FieldByName(name string) (StructField, bool)
. Вот пример того, как можно получить тег конкретного поля:package main
import (
"fmt"
"reflect"
"time"
)
type MyType struct {
User string `json:"user,omitempty" example:"Bob"`
CreatedAt time.Time `json:"created_at"`
}
const (
targetField = "User" // имя поля, о котором нужно получить информацию
targetTag = "json" // тег, значение которого нужно получить
)
func main() {
obj := MyType{}
// получаем Go-описание типа
objType := reflect.TypeOf(obj)
// ищем поле по имени
field, ok := objType.FieldByName(targetField)
if !ok {
panic(fmt.Errorf("field (%s): not found", targetField))
}
// ищем тег по имени
tagValue, ok := field.Tag.Lookup(targetTag)
if !ok {
panic(fmt.Errorf("tag (%s) for field (%s): not found", targetTag, targetField))
}
fmt.Printf("Значение тега %s поля %s: %s\n", targetTag, targetField, tagValue)
fmt.Printf("Теги поля %s: %s\n", targetField, field.Tag)
}
Значение тега json поля User: user,omitempty
Теги поля User: json:"user,omitempty" example:"Bob"
"name"
и "omitempty"
для поля User
), нужно дополнительно разобрать строку — это можно сделать функцией strings.Split(tagValue, ",")
.json
стандартной библиотеки. При операциях сериализации код пакета пробегает по всем полям структуры, а также рекурсивно по вложенным структурам, если они есть. Чтобы оптимизировать этот процесс, пакет использует кеширование: если он уже разобрал один раз определённый тип, то последующие операции сериализации с использованием этого типа (или его составляющих) пакет выполняет быстрее.nullable
и так далее. Для формирования SQL-запроса библиотека должна пробежать по всем полям типа и собрать нужные ей аннотации — например, получить все возможные имена столбцов для SELECT
-запроса. func init()
) — спарсить все теги один раз на старте, создать метахранилище и использовать готовые данные для формирования запроса или других операций.reflect
происходит в рантайме. Способа оптимизировать эту операцию нет. Поэтому любая работа с рефлексией — это всегда поиск баланса между удобством (generic code) и скоростью.json
-тегов. Код с таким типом скомпилируется, но сериализация будет работать не так, как ожидается, или приведёт к панике:type MyStruct struct {
// дубликат тега
ID int `json:"id" yaml:"id" json:"myid"`
// разделение запятыми, а не пробелом
Name string `json:"name", yaml:"name"`
// лишние пробелы
Key string `json: "key" yaml :"key"`
// перепутаны виды кавычек
Company string "json:`key`"
// encoding/json игнорирует неэкспортируемые поля
hidden string `json:"hidden"`
}
reflect.StructTag
по принципу «кто первый, тот и прав»: ищется первое вхождение искомого тега в набор.\
в значении тега останавливают парсинг строки, выполняемый методом StructTag.Lookup()
. Разработчик может не придерживаться принятых правил, но тогда он должен разбирать теги, не используя этот метод. Это плохая практика, лучше её избегать.-structtag
— она проверит, согласуются ли используемые теги с методом reflect.StructTag.Get()
.reflect
.json
, который использует структурные теги для определения параметров JSON-преобразования.