Почему появляется ошибка not a struct pointer в Go?
Только начал разбираться с Go и столкнулся с такой проблемой. Делаю YAML парсер, который заполняет структуру и потом подставляет дефолтные значения.
import (
"os"
"github.com/creasty/defaults"
"gopkg.in/yaml.v3"
)
type ConfigSchema struct {
Metadata MetadataSchema `yaml:"metadata"`
Fields []FieldSchema `yaml:"fields"`
}
type MetadataSchema struct {
Name string `yaml:"name"`
}
type FieldSchema struct {
Settings FieldSettingsSchema `yaml:"settings"`
Value string `yaml:"value"`
}
type FieldSettingsSchema struct {
Description string `yaml:"description"`
Enabled bool `yaml:"enabled" default:"true"`
}
func main() {
yamlPath := "C:/path/test.yml"
var configSchema ConfigSchema
_ = parse(yamlPath, configSchema)
}
func parse(filePath string, out interface{}) error {
fileContent, err := os.ReadFile(filePath)
if err != nil {
return err
}
err = yaml.Unmarshal(fileContent, &out)
if err != nil {
return err
}
err = defaults.Set(out) // <- "not a struct pointer"
if err != nil {
panic(err)
}
return nil
}
Файл парсится корректно, но при заполнении пустых значений почему-то появляется ошибка not a struct pointer. Можете подсказать как её можно исправить?
Думал, что может нужно также указывать ссылку в defaults.Set(&out), но ошибка остается.
Еще был вариант поместить инициализацию var configSchema ConfigSchema в функцию parse и возвращать (ConfigSchema, err). В таком случае всё работает корректно, но так есть привязка к конкретной структуре, но хочется реализовать более универсальный парсер.
Ответы (1 шт):
Смотрите, в чём дело.
Как defaults.Set проверяет вид содержимого:
- сначала проверяется, что содержимое является указателем,
- затем проверяется, что по указателю лежит объект вида
reflect.Struct
Когда вы в своём коде вызываете parse(yamlPath, configSchema), на стеке создаётся копия структуры configSchema, указатель на которую упаковывается в interface{}. При распаковке этого контейнера средствами reflect тип содержимого будет ConfigSchema. Когда вы передаёте такой out в defaults.Set, там проверят вид содержимого контейнера interface{} - окажется Struct, а не Ptr. Отсюда ошибка.
Почему в вашем случае не сработает defaults.Set(&out)? Причина в том, что defaults.Set получит объект типа *interface{}, а не *ConfigSchema: в силу статической типизации Go хранится только та типовая информация, которая приведена в тексте программы. Раз out имеет тип interface{}, то &out имеет тип *interface{}. defaults.Set отвергнет *interface{} на втором этапе: указатель указывает на объект вида Interface, а не Struct.
Вот пример, в котором defaults.Set не ругается.
func main() {
// ...
var cfg ConfigSchema
err = parse(bz, &cfg)
// ...
}
func parse(bytez []byte, out interface{}) error {
err := yaml.Unmarshal(bytez, out)
if err != nil {
return err
}
err = defaults.Set(out)
if err != nil {
panic(err)
}
return nil
}
Видите в чём разница? Внутри parse я использую только out, и не беру указатель на этот интерфейс. Указатель должен передаваться непосредственно в функцию.
Я передаю в функцию parse указатель на результат: err = parse(bz, &cfg). В контейнер упаковывается указатель на указатель, и при распаковке interface{} получается объект типа *ConfigSchema. Затем я передаю в defaults.Set сам объект out, который содержит указатель на структуру. Благодаря этому defaults.Set находит именно указатель на структуру и не возвращает ошибку.