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

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

Unmarshaling YAML into different struct based off YAML field

问题

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

数据的格式如下:

fetchers:
- type: "aws"
  config:
    omega: "lul"
- type: "kubernetes"
  config: 
    foo: "bar"

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

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

type kubernetesConfig struct {
	foo string `yaml:"foo"`
}
type awsConfig struct {
	omega string `yaml:"omega"`
}

var c struct {
	Fetchers []struct {
		Type   string      `yaml:"type"`
		Config interface{} `yaml:"config"`
	} `yaml:"fetchers"`
}

err := yaml.Unmarshal(data, &c)
if err != nil {
	log.Fatal(err)
}
for _, val := range c.Fetchers {
	switch val.Type {
	case "kubernetes":
		conf := val.Config.(kubernetesConfig)
		fmt.Println(conf.foo)
	case "aws":
		conf := val.Config.(awsConfig)
		fmt.Println(conf.omega)
	default:
		log.Fatalf("No matching type, was type %v", val.Type)
	}
}

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:

fetchers:
- type: "aws"
  config:
    omega: "lul"
- type: "kubernetes"
  config: 
    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"):

type kubernetesConfig struct {
	foo string `yaml:"foo"`
}
type awsConfig struct {
	omega string `yaml:"omega"`
}

var c struct {
	Fetchers []struct {
		Type   string      `yaml:"type"`
		Config interface{} `yaml:"config"`
	} `yaml:"fetchers"`
}

err := yaml.Unmarshal(data, &c)
if err != nil {
	log.Fatal(err)
}
for _, val := range c.Fetchers {
	switch val.Type {
	case "kubernetes":
		conf := val.Config.(kubernetesConfig)
		fmt.Println(conf.foo)
	case "aws":
		conf := val.Config.(awsConfig)
		fmt.Println(conf.omega)
	default:
		log.Fatalf("No matching type, was type %v", val.Type)
	}
}

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 的机制,但可以通过以下方式轻松复制该技术,具体步骤在 这里 中有详细说明:

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)
}

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

var fs struct {
    Configs []struct {
        Type   string     `yaml:"type"`
        Config RawMessage `yaml:"config"` // 延迟解组
    } `yaml:"fetchers"`
}

err = yaml.Unmarshal([]byte(data), &fs)
if err != nil {
    return
}

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

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

或者:

k8s := kubernetesConfig{} // 具体类型
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:

type RawMessage struct {
    unmarshal func(interface{}) error
}

func (msg *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error {
    msg.unmarshal = unmarshal
    return nil
}

// call this method later - when we know what concrete type to use
func (msg *RawMessage) Unmarshal(v interace{}) error {
    return msg.unmarshal(v)
}

So to leverage this in your case:

var fs struct {
	Configs []struct {
		Type   string     `yaml:"type"`
		Config RawMessage `yaml:"config"` // delay unmarshaling
	} `yaml:"fetchers"`
}

err = yaml.Unmarshal([]byte(data), &fs)
if err != nil {
	return
}

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

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

or:

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

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

答案2

得分: 2

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

type Fetcher struct {
	Type   string      `yaml:"type"`
	Config interface{} `yaml:"config"`
}

// 接口兼容性
var _ yaml.Unmarshaler = &Fetcher{}

func (f *Fetcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var t struct {
		Type string `yaml:"type"`
	}
	err := unmarshal(&t)
	if err != nil {
		return err
	}
	f.Type = t.Type
	switch t.Type {
	case "kubernetes":
		var c struct {
			Config kubernetesConfig `yaml:"config"`
		}
		err := unmarshal(&c)
		if err != nil {
			return err
		}
		f.Config = c.Config
	case "aws":
		var c struct {
			Config awsConfig `yaml:"config"`
		}
		err := unmarshal(&c)
		if err != nil {
			return err
		}
		f.Config = c.Config
	}
	return nil
}
英文:

Found the solution by implementing Unmarshaler Interface:

type Fetcher struct {
	Type   string      `yaml:"type"`
	Config interface{} `yaml:"config"`
}

// Interface compliance
var _ yaml.Unmarshaler = &Fetcher{}

func (f *Fetcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var t struct {
		Type string `yaml:"type"`
	}
	err := unmarshal(&t)
	if err != nil {
		return err
	}
	f.Type = t.Type
	switch t.Type {
	case "kubernetes":
		var c struct {
			Config kubernetesConfig `yaml:"config"`
		}
		err := unmarshal(&c)
		if err != nil {
			return err
		}
		f.Config = c.Config
	case "aws":
		var c struct {
			Config awsConfig `yaml:"config"`
		}
		err := unmarshal(&c)
		if err != nil {
			return err
		}
		f.Config = c.Config
	}
	return nil
}

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:

确定