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

时间:2018-08-02 14:32:24

标签: go struct reflection types yaml

我正在研究一个通用配置解析器,该解析器读取YAML配置文件并将结果存储在结构中。我希望解析器的类型不可知,并且我想实现一些替代逻辑,所以我使用反射。

下面是我正在研究的完整但非常简化的版本,它说明了调用yaml.Unmarshal时遇到的问题。如果我传递一个指向我创建的没有反射的结构的指针(示例代码中的base2 := TestConf{}),则它会按预期工作:强类型结构进入,强类型结构出现。

但是,如果传入通过反射创建的结构(示例代码中的base := reflect.New(configType).Elem().Interface()),则会传入一个结构并返回map[interface{}]interface{}。如您所见,我已尽力验证这两个结构是否相同,如果它们的类型不同或不是DeepEqual,则会感到恐慌。

目前,这让我头疼不已,但我可以解决它。我只想了解为什么会发生这种情况,也许想想办法解决。

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())
    }
}

要运行此文件,您还需要将此YAML文件保存在./config_test.yml

RequiredConfig:
  Environment: dev
Str1: String 1
Strptr1: String pointer 1
TimePtr: 2018-08-01T17:25:50.179949-04:00

我得到的输出:

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"

因此base2的行为符合预期。 base以某种方式转换为地图。

1 个答案:

答案 0 :(得分:2)

那是因为这一行:

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

此时实际上是键入interface {}。

如果您这样做:

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

您将获得预期的结果。

编辑地址回复

传递指针界面变元

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\"\n", 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\"\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())
    }
}

收益:

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"