I'm working on a generic config parser that reads a YAML config file and stores the result in a struct. I'd like for the parser to be type agnostic, and I want to implement some override logic, so I'm using reflection.
Below is a complete but very simplified version of what I'm working on, which illustrates the problem around the call to yaml.Unmarshal. If I pass in a pointer to a struct that I created without reflection (base2 := TestConf{}
in the example code), it works as expected: a strongly-typed struct goes in, and a strongly-typed struct comes out.
However, if I pass in a struct that I create with reflection (base := reflect.New(configType).Elem().Interface()
in the example code), I pass in a struct and get a map[interface{}]interface{}
back. As you can see, I've done my best to verify that the two structs are identical, panicking if their types are different or if they're not DeepEqual.
This is giving me a real headache at present, but I can work around it. I'd just like to understand why it's happening, and perhaps learn a way around it.
package main
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"time"
yaml "gopkg.in/yaml.v2"
)
type TestConf struct {
RequiredConfig `yaml:"RequiredConfig"`
Str1 string `yaml:"Str1"`
Strptr1 *string `yaml:"Strptr1"`
TimePtr *time.Time `yaml:"TimePtr"`
}
type RequiredConfig struct {
Environment string `yaml:"Environment"`
}
var BaseConfigPath = "./config_test.yml"
func main() {
conf := TestConf{}
LoadConfig(&conf)
}
func LoadConfig(target interface{}) {
targetActual := reflect.ValueOf(target).Elem()
configType := targetActual.Type()
base := reflect.New(configType).Elem().Interface()
base2 := TestConf{}
if reflect.TypeOf(base) != reflect.TypeOf(base2) {
panic("your argument is invalid")
}
if !reflect.DeepEqual(base, base2) {
panic("your argument is invalid")
}
if _, err := os.Stat(BaseConfigPath); !os.IsNotExist(err) {
raw, _ := ioutil.ReadFile(BaseConfigPath)
fmt.Printf("Before base Type: \"%v\", Kind: \"%v\"\n", reflect.TypeOf(base), reflect.ValueOf(base).Kind())
err = yaml.Unmarshal(raw, &base)
fmt.Printf("After base Type: \"%v\", Kind: \"%v\"\n", reflect.TypeOf(base), reflect.ValueOf(base).Kind())
fmt.Printf("Before base2 Type: \"%v\", Kind: \"%v\"\n", reflect.TypeOf(base2), reflect.ValueOf(base2).Kind())
err = yaml.Unmarshal(raw, &base2)
fmt.Printf("After base2 Type: \"%v\", Kind: \"%v\"\n", reflect.TypeOf(base2), reflect.ValueOf(base2).Kind())
}
}
To run this, you'll also need this YAML file saved at ./config_test.yml
:
RequiredConfig:
Environment: dev
Str1: String 1
Strptr1: String pointer 1
TimePtr: 2018-08-01T17:25:50.179949-04:00
The output I get:
Before base Type: "main.TestConf", Kind: "struct"
After base Type: "map[interface {}]interface {}", Kind: "map"
Before base2 Type: "main.TestConf", Kind: "struct"
After base2 Type: "main.TestConf", Kind: "struct"
So base2
acts as expected. base
somehow gets converted to a map.
Aucun commentaire:
Enregistrer un commentaire