Помогите разобраться с рефлексией в Go
Делаю домашку для себя (никто ее не проверяет), хочу понять что я делаю не так.
Задача такая:
Написать функцию, которая принимает на вход структуру in (struct или кастомную struct) и values map[string]interface{} (key - название поля структуры, которому нужно присвоить value этой мапы). Необходимо по значениям из мапы изменить входящую структуру in с помощью пакета reflect. Функция может возвращать только ошибку error.
Как я ее сделал:
package main
import (
"fmt"
"reflect"
)
// Написать функцию, которая принимает на вход структуру in (struct или кастомную struct) и
// values map[string]interface{} (key - название поля структуры, которому нужно присвоить
// value этой мапы). Необходимо по значениям из мапы изменить входящую структуру in с
// помощью пакета reflect. Функция может возвращать только ошибку error.
// создаю пустую структуру, которую потом буду менять
var inSt struct{}
func main() {
// создаю мапу в нужном формате и присваиваю ей значение
values := make(map[string]interface{})
values["one"] = 1
// вызываю функцию, которой передаю указатель на структуру и копию мапы
structEdit(&inSt, values)
// вывожу резульатат для проверки, должно быть {one 1}
fmt.Println(inSt) // {}
}
func structEdit(inStruct *struct{}, valuesMap map[string]interface{}) {
// присваиваем val значение мапы, напрямую с ней работать нельзя, т.к.
// у нее тип не reflect.value и соответственно нет методов рефлексии
val := reflect.ValueOf(valuesMap)
// присваиваем in значение пустой структуры, которую передали по указателю
// при этом разыменовываем ее и через Elem берем значения через указатель
// синактсис конечно странновато выглядит - сахара не хватает
in := reflect.ValueOf(&inStruct).Elem()
// здесь я беру цикл, цель которого - это получить значение key
// ну и посмотреть, а как вообще работать с key, полученной таким образом
// итого e - это key, а v - это значение мапы по этому key
for _, e := range val.MapKeys() {
v := val.MapIndex(e).Elem()
// тут мы программно создаем структуру, определяем типы данных
t := reflect.StructOf([]reflect.StructField{
{
Name: "Key",
Type: reflect.TypeOf(e.Interface()), // string
},
{
Name: "Value",
Type: reflect.TypeOf(v.Interface()), // int (это странно, ведь был interface{} у мапы
},
})
fmt.Println(t) // struct { Key string; Value int }
// тут мы создаем структуру и передаю значения
// вместо того чтобы напрямую указывать тип, можно сделать варианты по типам
in = reflect.New(t).Elem()
in.Field(0).SetString(e.String())
in.Field(1).SetInt(v.Int())
fmt.Println(in) // {one 1}
}
}
Вопрос: как мне записать результат в inSt. Я смог записать в in {one; 1} - но это не изменяет inSt?
Добавил комментариев в код - может это поможет. https://go.dev/play/p/vbeqklwfBf4
Ответы (1 шт):
Структуры в Golang создаются на этапе компиляции, и в рантайме невозможно добавить в структуру поле, или сохранить в поле значение другого типа. Поэтому ваша задача была в том, чтобы корректно обработать все ошибочные ситуации.
Я подправил ваш код, получилось вот что:
package main
import (
"fmt"
"reflect"
)
// Написать функцию, которая принимает на вход структуру in (struct или кастомную struct) и
// values map[string]interface{} (key - название поля структуры, которому нужно присвоить
// value этой мапы). Необходимо по значениям из мапы изменить входящую структуру in с
// помощью пакета reflect. Функция может возвращать только ошибку error.
// создаю пустую структуру, которую потом буду менять
var inSt struct {
One int
private int
}
func main() {
// создаю мапу в нужном формате и присваиваю ей значение
values := make(map[string]interface{})
values["One"] = 1
// Раскомментируйте эту строку, чтобы посмотреть на ошибку несовместимости типов
// values["One"] = "one"
// Раскомментируйте эту строку, чтобы посмотреть на ошибку присвоения приватного поля
// values["private"] = 1
// Раскомментируйте эту строку, чтобы посмотреть на ошибку присвоения несуществующего поля
// values["Two"] = 1
// вызываю функцию, которой передаю указатель на структуру и копию мапы
err := structEdit(&inSt, values)
if err != nil {
fmt.Println(err)
} else {
// вывожу резульатат для проверки, должно быть {1 0}
fmt.Println(inSt) // {1 0}
}
}
func structEdit(inStruct any, valuesMap map[string]interface{}) error {
v := reflect.ValueOf(inStruct)
// Аргумент должен быть указателем на структур
// указатель - для того, чтобы изменения сохранились после вызова функции
t := reflect.TypeOf(inStruct)
if t.Kind() != reflect.Pointer {
return fmt.Errorf("параметр должен быть указателем: %s", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return fmt.Errorf("параметр должен быть указателем на структуру: %s", t)
}
v = v.Elem()
// Перебираем пары {ключ,значение}
for key, value := range valuesMap {
field := v.FieldByName(key)
if !field.IsValid() {
return fmt.Errorf("нет такого поля: %s", key)
}
if !field.CanSet() {
return fmt.Errorf("приватное поле: %s", key)
}
if !field.Type().AssignableTo(reflect.TypeOf(value)) {
return fmt.Errorf("невозможно сохранить в поле типа %s значение типа %s", field.Type(), reflect.TypeOf(value))
}
field.Set(reflect.ValueOf(value))
}
return nil
}