当我通过反射构造Golang yaml.v2时,为什么将其变成地图?

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\"
", reflect.TypeOf(base), reflect.ValueOf(base).Kind())
        err = yaml.Unmarshal(raw, &base)
        fmt.Printf("After base Type: \"%v\", Kind: \"%v\"
", reflect.TypeOf(base), reflect.ValueOf(base).Kind())

        fmt.Printf("Before base2 Type: \"%v\", Kind: \"%v\"
", reflect.TypeOf(base2), reflect.ValueOf(base2).Kind())
        err = yaml.Unmarshal(raw, &base2)
        fmt.Printf("After base2 Type: \"%v\", Kind: \"%v\"
", 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.

That's because this line:

base := reflect.New(configType).Elem().Interface()

Is literally type interface{} at this point.

If you do this:

base, ok := base := reflect.New(configType).Elem().Interface().(TestConf)
if !ok{
    panic("got wrong type")
}
//rest of your code

You will get the results you expect.

Edit to address reply

Pass the pointer interface umarshal

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()
    baseReflect := reflect.New(configType)
    // Actual type.
    base := baseReflect.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\"
", reflect.TypeOf(base), reflect.ValueOf(base).Kind())
        // Passes the pointer to unmarshal
        err = yaml.Unmarshal(raw, baseReflect.Interface())
        fmt.Printf("After base Type: \"%v\", Kind: \"%v\"
", reflect.TypeOf(base), reflect.ValueOf(base).Kind())

        fmt.Printf("Before base2 Type: \"%v\", Kind: \"%v\"
", reflect.TypeOf(base2), reflect.ValueOf(base2).Kind())
        err = yaml.Unmarshal(raw, &base2)
        fmt.Printf("After base2 Type: \"%v\", Kind: \"%v\"
", reflect.TypeOf(base2), reflect.ValueOf(base2).Kind())
    }
}

Yields:

Before base Type: "main.TestConf", Kind: "struct"
After base Type: "main.TestConf", Kind: "struct"
Before base2 Type: "main.TestConf", Kind: "struct"
After base2 Type: "main.TestConf", Kind: "struct"