Reflection in Go
Type
: represents a type declaration in Go. Usingreflect.Type
You can learn about the characteristics of a type, such as its name, size, number of fields (if it is a structure), etc.Value
: represents the value of a variable. Usingreflect.Value
You can retrieve and modify the data stored in a variable.
Now let's take a look at some of the functions in the package. reflect
:
reflect.TypeOf()
: returnsreflect.Type
representing the type of the variable.reflect.ValueOf()
: returnsreflect.Value
representing the value of the variable.Interface()
: returnsinterface{}
representing the current value. This allows the original data to be retrieved fromreflect.Value
.Kind()
: returnsreflect.Kind
representing a specific data type, such asInt
,Float64
,Struct
etc.NumField()
AndField(i int)
: are used to work with structures, allow you to get the number of fields and access to each field respectively.NumMethod()
AndMethod(i int)
: used to work with methods, providing access to the method and its properties.
Example of use:
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
// получаем тип переменной
t := reflect.TypeOf(p)
fmt.Println("Тип:", t.Name()) // вывод: Тип: Person
// получаем значение переменной
v := reflect.ValueOf(p)
fmt.Println("Значение:", v) // вывод: Значение: {Alice 30}
// получаем количество полей
numFields := v.NumField()
fmt.Println("Количество полей:", numFields) // вывод: Количество полей: 2
// итерация по полям структуры
for i := 0; i < numFields; i++ {
field := v.Field(i)
fmt.Printf("Поле %d: %v\n", i, field)
// вывод: поле 0: Alice
// поле 1: 30
}
}
The main feature of reflection is the ability to work with types and values at runtime.
Several application examples
Automatic serialization and deserialization of structures
One of the most common use cases for reflection is automatic serialization and deserialization of data, especially in JSON format:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// MarshalStructToJSON принимает любую структуру и возвращает ее JSON-представление
func MarshalStructToJSON(s interface{}) ([]byte, error) {
// Получаем значение из интерфейса
v := reflect.ValueOf(s)
// Проверяем, что переданный параметр - структура
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct but got %s", v.Kind())
}
// Используем стандартную библиотеку для сериализации в JSON
return json.Marshal(s)
}
func main() {
user := User{Name: "John Doe", Email: "john@example.com", Age: 30}
jsonData, err := MarshalStructToJSON(user)
if err != nil {
fmt.Println("Ошибка сериализации:", err)
return
}
fmt.Println("JSON:", string(jsonData))
}
INMarshalStructToJSON
reflection is used to check that the passed parameter is a struct, and then the Go standard library is used to serialize the struct to JSON.
It is possible to automatically serialize any structure without knowing its exact type at compile time.
Structure validator using tags
Reflection allows you to create custom validators based on structure tags:
package main
import (
"errors"
"fmt"
"reflect"
"strings"
)
type Product struct {
Name string `validate:"required"`
Price float64 `validate:"min=0"`
}
// ValidateStruct принимает структуру и проверяет её поля на соответствие тэгам валидации
func ValidateStruct(s interface{}) error {
v := reflect.ValueOf(s)
t := reflect.TypeOf(s)
if v.Kind() != reflect.Struct {
return errors.New("expected struct")
}
for i := 0; i < v.NumField(); i++ {
fieldValue := v.Field(i)
fieldType := t.Field(i)
tag := fieldType.Tag.Get("validate")
if strings.Contains(tag, "required") && fieldValue.IsZero() {
return fmt.Errorf("поле %s обязательно", fieldType.Name)
}
if strings.Contains(tag, "min=0") && fieldValue.Kind() == reflect.Float64 && fieldValue.Float() < 0 {
return fmt.Errorf("значение поля %s должно быть неотрицательным", fieldType.Name)
}
}
return nil
}
func main() {
product := Product{Name: "", Price: -15.0}
err := ValidateStruct(product)
if err != nil {
fmt.Println("Ошибка валидации:", err)
} else {
fmt.Println("Структура валидна")
}
}
IN ValidateStruct
reflection is used to extract and validate the values of structure fields based on tags.
Tags allow you to specify additional validation rules, such as whether a field is required. required
or minimum value min=0
.
Dynamic creation of type instances
Reflection allows you to create instances of structures and types on the fly:
package main
import (
"fmt"
"reflect"
)
type Order struct {
ID int
Total float64
}
func CreateInstanceOfType(t reflect.Type) interface{} {
return reflect.New
}
func main() {
orderType := reflect.TypeOf(Order{})
order := CreateInstanceOfType(orderType).(Order)
fmt.Printf("Созданный экземпляр: %+v\n", order)
}
Function CreateInstanceOfType
creates a new instance of the structure using reflect.New
.
Calling methods using reflection
Reflection allows you to call methods dynamically:
package main
import (
"fmt"
"reflect"
)
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
func CallMethod(obj interface{}, methodName string, args ...interface{}) (interface{}, error) {
v := reflect.ValueOf(obj)
method := v.MethodByName(methodName)
if !method.IsValid() {
return nil, fmt.Errorf("метод %s не найден", methodName)
}
methodArgs := make([]reflect.Value, len(args))
for i, arg := range args {
methodArgs[i] = reflect.ValueOf(arg)
}
result := method.Call(methodArgs)
if len(result) > 0 {
return result[0].Interface(), nil
}
return nil, nil
}
func main() {
calc := Calculator{}
sum, err := CallMethod(calc, "Add", 5, 3)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("Сумма:", sum)
product, err := CallMethod(calc, "Multiply", 5, 3)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("Произведение:", product)
}
Function CallMethod
dynamically calls an object's method using MethodByName
.
We use reflect.ValueOf
to obtain the method and Call
to call him.
Inspection and manipulation of structure fields
Reflection allows you to dynamically change the values of fields in structures:
package main
import (
"fmt"
"reflect"
)
type Employee struct {
Name string
Salary float64
}
func SetFieldValue(obj interface{}, fieldName string, value interface{}) error {
v := reflect.ValueOf(obj).Elem()
field := v.FieldByName(fieldName)
if !field.IsValid() {
return fmt.Errorf("поле %s не найдено", fieldName)
}
if !field.CanSet() {
return fmt.Errorf("поле %s не может быть изменено", fieldName)
}
field.Set(reflect.ValueOf(value))
return nil
}
func main() {
emp := Employee{Name: "Alice", Salary: 50000}
fmt.Println("До изменения:", emp)
err := SetFieldValue(&emp, "Salary", 60000)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("После изменения:", emp)
}
Function SetFieldValue
changes the value of a structure field using FieldByName
.
We use CanSet
to check if the field can be changed.
More about reflect you can find out here.
OTUS experts tell more about programming languages in practical online courses. You can see the full catalog of courses see the link.