将YAML解组为基于YAML字段的不同结构体

huangapple go评论122阅读模式
英文:

Unmarshaling YAML into different struct based off YAML field

问题

我正在尝试将以下YAML数据解组为Go结构。

数据的格式如下:

  1. fetchers:
  2. - type: "aws"
  3. config:
  4. omega: "lul"
  5. - type: "kubernetes"
  6. config:
  7. foo: "bar"

根据type字段,我想确定是否将config字段解组为awsConfigkubernetesConfig结构。

我的当前代码如下(使用"gopkg.in/yaml.v2"):

  1. type kubernetesConfig struct {
  2. foo string `yaml:"foo"`
  3. }
  4. type awsConfig struct {
  5. omega string `yaml:"omega"`
  6. }
  7. var c struct {
  8. Fetchers []struct {
  9. Type string `yaml:"type"`
  10. Config interface{} `yaml:"config"`
  11. } `yaml:"fetchers"`
  12. }
  13. err := yaml.Unmarshal(data, &c)
  14. if err != nil {
  15. log.Fatal(err)
  16. }
  17. for _, val := range c.Fetchers {
  18. switch val.Type {
  19. case "kubernetes":
  20. conf := val.Config.(kubernetesConfig)
  21. fmt.Println(conf.foo)
  22. case "aws":
  23. conf := val.Config.(awsConfig)
  24. fmt.Println(conf.omega)
  25. default:
  26. log.Fatalf("No matching type, was type %v", val.Type)
  27. }
  28. }

Playground中的代码:https://go.dev/play/p/klxOoHMCtnG

目前它被解组为map[interface {}]interface {},无法转换为上述结构之一。
错误:
panic: interface conversion: interface {} is map[interface {}]interface {}, not main.awsConfig

我是否需要使用自定义的UnmarshalYAML函数来实现YAML包的Unmarshaler接口以完成此操作?

英文:

I'm trying to unmarshal the following YAML data into Go structures.

The data is the in the following format:

  1. fetchers:
  2. - type: "aws"
  3. config:
  4. omega: "lul"
  5. - type: "kubernetes"
  6. config:
  7. foo: "bar"

Based of the type field, I want to determine wether to unmarshal the config field into awsConfig or kubernetesConfig struct.

My current code looks like this (using "gopkg.in/yaml.v2"):

  1. type kubernetesConfig struct {
  2. foo string `yaml:"foo"`
  3. }
  4. type awsConfig struct {
  5. omega string `yaml:"omega"`
  6. }
  7. var c struct {
  8. Fetchers []struct {
  9. Type string `yaml:"type"`
  10. Config interface{} `yaml:"config"`
  11. } `yaml:"fetchers"`
  12. }
  13. err := yaml.Unmarshal(data, &c)
  14. if err != nil {
  15. log.Fatal(err)
  16. }
  17. for _, val := range c.Fetchers {
  18. switch val.Type {
  19. case "kubernetes":
  20. conf := val.Config.(kubernetesConfig)
  21. fmt.Println(conf.foo)
  22. case "aws":
  23. conf := val.Config.(awsConfig)
  24. fmt.Println(conf.omega)
  25. default:
  26. log.Fatalf("No matching type, was type %v", val.Type)
  27. }
  28. }

Code in playground: https://go.dev/play/p/klxOoHMCtnG

Currently it gets unmarshalled as map[interface {}]interface {}, which can't be converted to one of the structs above.
Error:
panic: interface conversion: interface {} is map[interface {}]interface {}, not main.awsConfig \

Do I have to implemented the Unmarshaler Interface of the YAML package with a custom UnmarshalYAML function to get this done?

答案1

得分: 3

这种任务类型——延迟解组(unmarshaling)——与 json.RawMessage 的工作方式非常相似,可以参考 这个示例

yaml 包没有类似 RawMessage 的机制,但可以通过以下方式轻松复制该技术,具体步骤在 这里 中有详细说明:

  1. type RawMessage struct {
  2. unmarshal func(interface{}) error
  3. }
  4. func (msg *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error {
  5. msg.unmarshal = unmarshal
  6. return nil
  7. }
  8. // 在稍后调用此方法时,我们会知道要使用的具体类型
  9. func (msg *RawMessage) Unmarshal(v interface{}) error {
  10. return msg.unmarshal(v)
  11. }

因此,在你的情况下可以这样使用:

  1. var fs struct {
  2. Configs []struct {
  3. Type string `yaml:"type"`
  4. Config RawMessage `yaml:"config"` // 延迟解组
  5. } `yaml:"fetchers"`
  6. }
  7. err = yaml.Unmarshal([]byte(data), &fs)
  8. if err != nil {
  9. return
  10. }

根据配置的 "Type"(awskubernetes),你最终可以将 RawMessage 解组为正确的具体类型:

  1. aws := awsConfig{} // 具体类型
  2. err = c.Config.Unmarshal(&aws)

或者:

  1. k8s := kubernetesConfig{} // 具体类型
  2. err = c.Config.Unmarshal(&k8s)

这里有一个可工作的示例:https://go.dev/play/p/wsykOXNWk3H

英文:

This type of task - where you want to delay the unmarshaling - is very similar to how json.RawMessage works with examples like this.

The yaml package does not have a similar mechanism for RawMessage - but this technique can easily be replicated as outlined here:

  1. type RawMessage struct {
  2. unmarshal func(interface{}) error
  3. }
  4. func (msg *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error {
  5. msg.unmarshal = unmarshal
  6. return nil
  7. }
  8. // call this method later - when we know what concrete type to use
  9. func (msg *RawMessage) Unmarshal(v interace{}) error {
  10. return msg.unmarshal(v)
  11. }

So to leverage this in your case:

  1. var fs struct {
  2. Configs []struct {
  3. Type string `yaml:"type"`
  4. Config RawMessage `yaml:"config"` // delay unmarshaling
  5. } `yaml:"fetchers"`
  6. }
  7. err = yaml.Unmarshal([]byte(data), &fs)
  8. if err != nil {
  9. return
  10. }

and based on the config "Type" (aws or kubernetes), you can finally unmarshal the RawMessage into the correct concrete type:

  1. aws := awsConfig{} // concrete type
  2. err = c.Config.Unmarshal(&aws)

or:

  1. k8s := kubernetesConfig{} // concrete type
  2. err = c.Config.Unmarshal(&k8s)

Working example here: https://go.dev/play/p/wsykOXNWk3H

答案2

得分: 2

通过实现Unmarshaler接口找到了解决方案:

  1. type Fetcher struct {
  2. Type string `yaml:"type"`
  3. Config interface{} `yaml:"config"`
  4. }
  5. // 接口兼容性
  6. var _ yaml.Unmarshaler = &Fetcher{}
  7. func (f *Fetcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
  8. var t struct {
  9. Type string `yaml:"type"`
  10. }
  11. err := unmarshal(&t)
  12. if err != nil {
  13. return err
  14. }
  15. f.Type = t.Type
  16. switch t.Type {
  17. case "kubernetes":
  18. var c struct {
  19. Config kubernetesConfig `yaml:"config"`
  20. }
  21. err := unmarshal(&c)
  22. if err != nil {
  23. return err
  24. }
  25. f.Config = c.Config
  26. case "aws":
  27. var c struct {
  28. Config awsConfig `yaml:"config"`
  29. }
  30. err := unmarshal(&c)
  31. if err != nil {
  32. return err
  33. }
  34. f.Config = c.Config
  35. }
  36. return nil
  37. }
英文:

Found the solution by implementing Unmarshaler Interface:

  1. type Fetcher struct {
  2. Type string `yaml:"type"`
  3. Config interface{} `yaml:"config"`
  4. }
  5. // Interface compliance
  6. var _ yaml.Unmarshaler = &Fetcher{}
  7. func (f *Fetcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
  8. var t struct {
  9. Type string `yaml:"type"`
  10. }
  11. err := unmarshal(&t)
  12. if err != nil {
  13. return err
  14. }
  15. f.Type = t.Type
  16. switch t.Type {
  17. case "kubernetes":
  18. var c struct {
  19. Config kubernetesConfig `yaml:"config"`
  20. }
  21. err := unmarshal(&c)
  22. if err != nil {
  23. return err
  24. }
  25. f.Config = c.Config
  26. case "aws":
  27. var c struct {
  28. Config awsConfig `yaml:"config"`
  29. }
  30. err := unmarshal(&c)
  31. if err != nil {
  32. return err
  33. }
  34. f.Config = c.Config
  35. }
  36. return nil
  37. }

huangapple
  • 本文由 发表于 2022年1月9日 03:20:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/70635636.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定