Помогите разобраться с рефлексией в 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 шт):

Автор решения: Pak Uula

Структуры в 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
}
→ Ссылка