猿问

强制解组为 interface{} 而不是 map[string]interface{}

我有以下 YAML 结构:


type Pipeline struct {

    Name string                  `yaml:"name"`

    Nodes map[string]NodeConfig  `yaml:"nodes"`

    Connections []NodeConnection `yaml:"connections"`

}


type NodeConfig struct {

    Type   string      `yaml:"type"`

    Config interface{} `yaml:"config"`

}

对于每个NodeConfig,根据 的值Type,我需要检测 的真实类型Config。


switch nc.Type {

    case "request":

        return NewRequestNode(net, name, nc.Config.(RequestConfig))

    case "log":

        return NewLogNode(net, name)

    //...

}

这是我从中得到的错误:


panic: interface conversion: interface {} is map[string]interface {}, not main.RequestConfig

我怀疑这是因为当我真的Config希望map[string]interface{}它只是一个interface{}. 我怎样才能做到这一点?


牛魔王的故事
浏览 286回答 1
1回答

慕哥9229398

你对这个问题是正确的,它被自动识别为一个map[string]interface{},因为你没有提供自定义 UnmarshalYAML func YAML 包只能这样做。但是您实际上不希望它只是interface{},您需要确定您想要的实际实现。使用 yaml.v3 的解决方案UnmarshalYAML如果不提供自定义函数来键入,我看不出如何解决它NodeConfig。如果那是 JSON,我会读作Configa json.RawMessage,然后对于每种可能的类型,我会将其解组为所需的类型,而 yaml.v3 等效项似乎是yaml.Node 类型。使用它,您可以创建一个类似于NodeConfig具有Configas 的结构yaml.Node,并根据Type值将其转换为具体类型,如下所示:func (nc *NodeConfig) UnmarshalYAML(value *yaml.Node) error {    var ncu struct {        Type   string    `yaml:"type"`        Config yaml.Node `yaml:"config"`    }    var err error    // unmarshall into a NodeConfigUnmarshaler to detect correct type    err = value.Decode(&ncu)    if err != nil {        return err    }    // now, detect the type and covert it accordingly    nc.Type = ncu.Type    switch ncu.Type {    case "request":        nc.Config = &RequestConfig{}    case "log":        nc.Config = &LogConfig{}    default:        return fmt.Errorf("unknown type %q", ncu.Type)    }    err = ncu.Config.Decode(nc.Config)    return err}示例代码为了测试这一点,我创建了假人和RequestConfig一个LogConfig样本:type RequestConfig struct {    Foo string `yaml:"foo"`    Bar string `yaml:"bar"`}type LogConfig struct {    Message string `yaml:"message"`}func main() {    logSampleYAML := []byte(`type: logconfig:    message: this is a log message`)    reqSampleYAML := []byte(`type: requestconfig:    foo: foo value    bar: bar value`)    for i, val := range [][]byte{logSampleYAML, reqSampleYAML} {        var nc NodeConfig        err := yaml.Unmarshal(val, &nc)        if err != nil {            fmt.Printf("failed to parse sample %d: %v\n", i, err)        } else {            fmt.Printf("sample %d type %q (%T) = %+v\n", i, nc.Type, nc.Config, nc.Config)        }    }}哪些输出:sample 0 type "log" (*main.LogConfig) = &{Message:this is a log message}sample 1 type "request" (*main.RequestConfig) = &{Foo:foo value Bar:bar value}因此,正如您所看到的,每个实例都使用所需的具体类型实例NodeConfig化了,这意味着您现在可以将类型断言用作or (当然是or )。ConfigConfg.(*RequestConfig)Config.(*LogConfig)switch您可以在这个 Go Playground 完整示例中使用该解决方案。使用 yaml.v2 的解决方案我犯了一个错误并发送了 v2 的解决方案,但我建议任何人使用 v3。如果你不能,请按照v2版本...v2 没有,但我在这个问题yaml.Node的答案中找到了一个非常相似的解决方案(我在那里修正了一个错字):type RawMessage struct {    unmarshal func(interface{}) error}func (msg *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error {    msg.unmarshal = unmarshal    return nil}func (msg *RawMessage) Unmarshal(v interface{}) error {    return msg.unmarshal(v)}这是一个有趣的技巧,你可以通过将UnmarshalYAML它加载到一个临时结构中来烘焙你自己的函数,然后识别你想要的每种类型,而无需处理 YAML 两次:func (nc *NodeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {    var ncu struct {        Type   string     `yaml:"type"`        Config RawMessage `yaml:"config"`    }    var err error    // unmarshall into a NodeConfigUnmarshaler to detect correct type    err = unmarshal(&ncu)    if err != nil {        return err    }    // now, detect the type and covert it accordingly    nc.Type = ncu.Type    switch ncu.Type {    case "request":        cfg := &RequestConfig{}        err = ncu.Config.Unmarshal(cfg)        nc.Config = cfg    case "log":        cfg := &LogConfig{}        err = ncu.Config.Unmarshal(cfg)        nc.Config = cfg    default:        return fmt.Errorf("unknown type %q", ncu.Type)    }    return err}v2 和 v3 的示例代码是相同的。
随时随地看视频慕课网APP

相关分类

Go
我要回答