В данной статье разберём что такое структурные теги (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(). У нас же значение по ссылке.