英文:
Force unmarshal as interface{} instead of 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{}
。我该如何解决这个问题?
编辑:最小示例
英文:
I have the following YAML structure:
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"`
}
For each NodeConfig
, depending on the value of Type
, I need to detect the real type of Config
.
switch nc.Type {
case "request":
return NewRequestNode(net, name, nc.Config.(RequestConfig))
case "log":
return NewLogNode(net, name)
//...
}
This is the error I get from this:
panic: interface conversion: interface {} is map[string]interface {}, not main.RequestConfig
I suspect it's because Config
is getting automatically recognized as a map[string]interface{}
, when I really want it to just be an interface{}
. How can I do this?
Edit: Minimal Example
答案1
得分: 1
你对问题的理解是正确的,它被自动识别为map[string]interface{}
,因为你没有提供自定义的UnmarshalYAML
函数,YAML包只能这样处理。但实际上你不希望它只是interface{}
,你需要确定你想要的实际实现。
使用yaml.v3的解决方案
我认为你无法在不提供自定义的UnmarshalYAML
函数给NodeConfig
类型的情况下解决这个问题。如果是JSON,我会将Config
读取为json.RawMessage
,然后对每种可能的类型进行解组,而yaml.v3的等效方式似乎是使用yaml.Node类型。
使用这种方式,你可以创建一个类似于NodeConfig
的结构体,其中Config
是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
// 解组为NodeConfigUnmarshaler以检测正确的类型
err = value.Decode(&ncu)
if err != nil {
return err
}
// 现在,根据类型进行转换
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
实例都使用所需的具体类型实例化了Config
,这意味着你现在可以使用类型断言,如Config.(*RequestConfig)
或Config.(*LogConfig)
(或使用switch
语句,当然也可以)。
你可以在这个Go Playground完整示例中尝试这个解决方案。
使用yaml.v2的解决方案
我犯了一个错误,并发送了一个使用v2的解决方案,但我建议任何人使用v3。如果你不能使用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)
}
这是一个有趣的技巧,通过将其加载到临时结构体中,然后识别每个你想要的类型,而无需处理两次YAML,你可以自己烘烤UnmarshalYAML
函数:
func (nc *NodeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
var ncu struct {
Type string `yaml:"type"`
Config RawMessage `yaml:"config"`
}
var err error
// 解组为NodeConfigUnmarshaler以检测正确的类型
err = unmarshal(&ncu)
if err != nil {
return err
}
// 现在,根据类型进行转换
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的示例代码是相同的。
你可以在这个Go Playground完整示例中尝试这个解决方案。
英文:
You are correct about the problem, it is getting automatically recognized as a map[string]interface{}
, since you don't provide a custom UnmarshalYAML func the YAML package can only do that. But you actually don't want it as just interface{}
, you need to identify which actual implementation you want for that.
Solution using yaml.v3
I don't see how you can solve it without providing a custom UnmarshalYAML
func to NodeConfig
type. If that was JSON, I would read the Config
as a json.RawMessage
, then for each possible type I would unmarshal it into the desired type, and yaml.v3 equivalent seems to be a yaml.Node type.
Using this, you can create a struct similar to NodeConfig
which has the Config
as yaml.Node
and convert it to the concrete type based on the Type
value, like this:
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
}
Sample code
To test that, I created dummies RequestConfig
and LogConfig
and a sample:
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)
}
}
}
Which outputs:
sample 0 type "log" (*main.LogConfig) = &{Message:this is a log message}
sample 1 type "request" (*main.RequestConfig) = &{Foo:foo value Bar:bar value}
So, as you can see each instance of NodeConfig
is instanciating the Config
with the concrete type required, which means you can now use type assertion as Confg.(*RequestConfig)
or Config.(*LogConfig)
(or switch
, of course).
You can play with that solution in this Go Playground full sample.
Solution using yaml.v2
I have made a mistake and sent a solution with v2, but I recommend anyone to use the v3. If you can't, follow the v2 version...
The v2 does not have yaml.Node
, but I found a very similar solution in the answer of this issue (I fixed a typo there):
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)
}
Which is an interesting trick, and with that you could bake your own UnmarshalYAML
func by loading it into a temporary struct and then identifying each type you want and without needing to process the YAML twice:
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
}
The sample code for v2 and v3 are identical.
You can play with that solution in this Go Playground full sample.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论