我有以下 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{}. 我怎樣才能做到這一點?
編輯:最小示例
uj5u.com熱心網友回復:
你對這個問題是正確的,它被自動識別為 a map[string]interface{},因為你不提供自定義 UnmarshalYAML func YAML 包只能這樣做。但您實際上并不想要它interface{},您需要確定您想要的實際實作。
使用 yaml.v3 的解決方案
如果不提供要鍵入的自定義UnmarshalYAML函式,我看不出如何解決它。NodeConfig如果那是 JSON,我會將其讀取Config為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都使用所需的具體型別來實體化Config,這意味著您現在可以將型別斷言用作Confg.(*RequestConfig)or 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通過將其加載到臨時結構中然后識別您想要的每種型別來烘焙您自己的 func,而無需處理 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 的示例代碼是相同的。
您可以在此 Go Playground 完整示例中使用該解決方案。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/512036.html
標籤:去
