Reflection in Go

  • Type: represents a type declaration in Go. Using reflect.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. Using reflect.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:

  1. reflect.TypeOf(): returns reflect.Typerepresenting the type of the variable.

  2. reflect.ValueOf(): returns reflect.Valuerepresenting the value of the variable.

  3. Interface(): returns interface{}representing the current value. This allows the original data to be retrieved from reflect.Value.

  4. Kind(): returns reflect.Kindrepresenting a specific data type, such as Int, Float64, Structetc.

  5. NumField() And Field(i int): are used to work with structures, allow you to get the number of fields and access to each field respectively.

  6. NumMethod() And Method(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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *