为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
牛魔王的故事 2023-02-21 19:03:22
我有以下 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{}. 我怎样才能做到这一点?
查看完整描述

1 回答

?
慕哥9229398

TA贡献1877条经验 获得超6个赞

你对这个问题是正确的,它被自动识别为一个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: log

config:

    message: this is a log message

`)


    reqSampleYAML := []byte(`

type: request

config:

    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 的示例代码是相同的。


查看完整回答
反对 回复 2023-02-21
  • 1 回答
  • 0 关注
  • 285 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信