Опубликовано: 25 сентября 2022 г. 21:30 | Автор: echodiv | Категория: Разработка | 👁 1391

Структурные теги в golang

В данной статье разберём что такое структурные теги (struct tag) и как их готовить.
В общих словах структурные теги - это элемент магии Go, который делает поведение вашего кода менее явным.
На них можно повесить какую-то логику обработки структур, как, например, сериализация в json. Указав их для полей структуры.
 
Тег внутри структуры выглядит следующим образом:
type S struct {
    Field int `tagname:"uno,dos"`
}
 
Где S - имя структуры, содержащей одно поле "Field". В свою очередь, поле имеет тег tagname со значением "uno,dos".
Имя поля не обязательно должно быть публичным, то есть начинаться с большой буквы, чтобы код, который будет обрабатывать теги имел доступ к полю.
 
Посмотрим на самый простой пример использования тегов. Будем просто проверять его наличие. Важно понимать, что для правильного распознования тега,
необходимо чтобы он соответствовал синтаксису. То есть он должен содержать ключ и значение, разделенные запятой. Значение, в свою очередь,
оборачивается в двойный ковычки.
 
type ToPrint struct {
    Prined     int `printed:""`
    NotPrinted int
}
 
func print() {
    tosi := ToPrint{88, 99}
 
    bosi_type := reflect.TypeOf(tosi)
    bosi_value := reflect.ValueOf(tosi)
    for i := 0; i < bosi_type.NumField(); i++ {
        field := bosi_type.Field(i)
        if tag_value, ok := field.Tag.Lookup("printed"); ok {
            fmt.Println(bosi_value.Field(i).Int(), tag_value)
        }
    }
 
}
 
Данный код выведет на экран только 88. Как мы видим значение может быть пустым, но оно должно быть.
Значение тега может быть простым и составным, что весьма условно. Так как это просто строка, то разделитель может быть любым. Обычно используют запятую.
Это можно увидеть на примере omiempty в тегах json.

 

Реализуем код, который будет производить математические операции с полями структуры
в зависимости от тегов. Пусть это будет инкремент, возведение в степень и деление. Писать результат будем в ту же структуру.
 
Структура и теги у нас будут следующими:
type Math struct {
    SomeField int     `math:"increment"`
    ToSquare  float64 `math:"pow,2"`
}
 
Сам код не будет сильно отличаться от первого примера, который, как мне кажется, исчерпывающий. Но так как я практикуюсь в слепой печати, то
позволю себе немного графомании.
func parseTag(tag string) (string, string) {
    tag, opt, _ := strings.Cut(tag, ",")
    return tag, opt
}
 
func mathTags(mathStruct *Math) {
    bosiType := reflect.TypeOf(*mathStruct)
    bosiValue := reflect.ValueOf(mathStruct)
 
    for i := 0; i < bosiType.NumField(); i++ {
        value := bosiType.Field(i).Tag.Get("math")
        if value == "" {
            return
        }
        field := bosiValue.Elem().Field(i)
        // Не пишите такие названия (value -> val -> v) %)
        val, opts := parseTag(value)
        if field.CanSet() {
            switch val {
            case "increment":
                field.SetInt(bosiValue.Elem().Field(i).Int() + 1)
            case "pow":
                whatIsMyName, _ := strconv.ParseFloat(opts, 8)
                field.SetFloat(math.Pow(bosiValue.Elem().Field(i).Float(), whatIsMyName))
            }
        }
    }
}
 
func main() {
    tosi := Math{1, 2}
    fmt.Printf("%#v\n", tosi)
    mathTags(&tosi)
    fmt.Printf("%#v\n", tosi)
}
Вывод после исполнения кода сверху будет следующим:

 

main.Math{SomeField:1, ToSquare:2}
main.Math{SomeField:2, ToSquare:4}
 
Тут важно передавать аргумент в mathTag по ссылке чтобы он был доступен для изменеия. И обратите внимание на получение значения поля
внутри конструкции switch. Field() должен проходить через Elem(). У нас же значение по ссылке.