将YAML解组为复杂对象,该对象可以是结构体或字符串。

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

Unmarshal YAML into complex object which may be either struct or string

问题

尝试将YAML解组为复杂对象,例如map[string]map[interface{}]string。问题在于,我希望能够区分stringSource之间的interface{}部分,而Source是一个结构体。

显然,YAML不知道如何解组为Source结构体,所以我必须实现Unmarshaler接口:

type Unmarshaler interface {
    UnmarshalYAML(value *Node) error
}

但是,我不太理解解组过程的整体情况。一般来说,我认为我必须手动遍历*yaml.Node并在每个节点上调用func UnmarshalYAML(value *Node) error

你可以在Go Playground上查看完整的代码和运行结果:playground链接

英文:

Trying to unmarshal YAML into complex object such as map[string]map[interface{}]string.
The problem is that I want to be able to differentiate an interface{} part between string and Source which is a struct.

type Source struct {
	ID     string `yaml:"id"`
	Name   string `yaml:"name"`
	LogoID string `yaml:"logoId"`
	URL    string `yaml:"url"`
}

type UNFT struct {
    ItemMeta map[string]map[interface{}]string `yaml:"item_meta"`
    // could be
    // ItemMeta map[string]map[string]string `yaml:"item_meta"`
    // or
    // ItemMeta map[string]map[Source]string `yaml:"item_meta"`
}

Obviously YAML does not know how to unmarshal into Source struct so I have to implement Unmarshaler interface:

type Unmarshaler interface {
	UnmarshalYAML(value *Node) error
}

But I don't quite understand the big picture of unmarshaling process. In general I assume that I have to manually traverse *yaml.Node and call func UnmarshalYAML(value *Node) error on every node.

package main

import (
	"fmt"

	"gopkg.in/yaml.v3"
)

type Source struct {
    ID     string `json:"id"`
    Name   string `json:"name"`
    LogoID string `json:"logoId"`
    URL    string `json:"url"`
}

var data = `
unf:
    item_meta:
      source:
           !struct
           ? id: "data-watch" 
             name: "DataWatch"
             logoid: "data-watch"
             url: "https"
           : "product_any('SS')"
      public_usage:
        "": "source_any('SDF')"
        "provider": "source_any('ANO')"`

type UNFT struct {
	ItemMeta map[string]map[interface{}]string `yaml:"item_meta"`
}

type MetaConverterConfigT struct {
	UNFT UNFT `yaml:"unf"`
}

func main() {
	cfg := MetaConverterConfigT{}

	err := yaml.Unmarshal([]byte(data), &cfg)
	if err != nil {
		fmt.Println("%w", err)
	}

	fmt.Println(cfg)
}

func (s *UNFT) UnmarshalYAML(n *yaml.Node) error {
	var cfg map[string]map[interface{}]string
	if err := n.Decode(&cfg); err != nil {
		fmt.Println("%w", err)
	}

	return nil
}

Go playground

答案1

得分: 1

type MetaKey struct {
	String string
	Source Source
}

func (k *MetaKey) UnmarshalYAML(n *yaml.Node) error {
	if n.Tag == "!!str" {
		return n.Decode(&k.String)
	}
	if n.Tag == "!!map" {
		return n.Decode(&k.Source)
	}
	return fmt.Errorf("unsupported MetaKey type")
}

// ...

type UNFT struct {
	ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
}

如果你希望保持map类型不变,即不添加自定义键类型,那么你也可以在UNFT上实现解组函数,并使用any进行重新映射:

type UNFT struct {
	ItemMeta map[string]map[any]string `yaml:"item_meta"`
}

func (u *UNFT) UnmarshalYAML(n *yaml.Node) error {
	var obj struct {
		ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
	}
	if err := n.Decode(&obj); err != nil {
		return err
	}

	u.ItemMeta = make(map[string]map[any]string, len(obj.ItemMeta))
	for k, v := range obj.ItemMeta {
		m := make(map[any]string, len(v))
		for k, v := range v {
			if k.Source != (Source{}) {
				m[k.Source] = v
			} else {
				m[k.String] = v
			}
		}
		u.ItemMeta[k] = m
	}
	return nil
}

请注意,以上代码是对给定代码的翻译,不包含任何其他内容。

英文:
type MetaKey struct {
	String string
	Source Source
}

func (k *MetaKey) UnmarshalYAML(n *yaml.Node) error {
	if n.Tag == "!!str" {
		return n.Decode(&k.String)
	}
	if n.Tag == "!!map" {
		return n.Decode(&k.Source)
	}
	return fmt.Errorf("unsupported MetaKey type")
}

// ...

type UNFT struct {
	ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
}

https://go.dev/play/p/Nhtab4l-ANT


If you need the map type to remain as is, i.e. without adding the custom key type, then you can implement the unmarshaler on UNFT as well and just do a re-mapping with any:

type UNFT struct {
	ItemMeta map[string]map[any]string `yaml:"item_meta"`
}

func (u *UNFT) UnmarshalYAML(n *yaml.Node) error {
	var obj struct {
		ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
	}
	if err := n.Decode(&obj); err != nil {
		return err
	}

	u.ItemMeta = make(map[string]map[any]string, len(obj.ItemMeta))
	for k, v := range obj.ItemMeta {
		m := make(map[any]string, len(v))
		for k, v := range v {
			if k.Source != (Source{}) {
				m[k.Source] = v
			} else {
				m[k.String] = v
			}
		}
		u.ItemMeta[k] = m
	}
	return nil
}

https://go.dev/play/p/uwboGKf3qnD

huangapple
  • 本文由 发表于 2023年1月26日 17:46:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/75244452.html
匿名

发表评论

匿名网友

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

确定