将 yaml 字段动态解析为 Go 中的有限结构集之一

我有一个文件,其中一个字段可以由一种可能的结构表示。为了简化代码和 yaml 文件,假设我有这些 yaml 文件:yaml


kind: "foo"

spec:

  fooVal: 4

kind: "bar"

spec:

  barVal: 5

以及这些用于解析的结构:


    type Spec struct {

        Kind string      `yaml:"kind"`

        Spec interface{} `yaml:"spec"`

    }

    type Foo struct {

        FooVal int `yaml:"fooVal"`

    }

    type Bar struct {

        BarVal int `yaml:"barVal"`

    }

我知道我可以用作一种字段类型。但是实际的例子更复杂,并且涉及更多可能的结构类型,而不仅仅是和,这就是为什么我不喜欢解析到该领域。map[string]interface{}SpecFooBarspec


我找到了一个解决方法:将yaml解构到中间结构中,然后检查字段,将字段封送到yaml后面,然后将其取消到具体类型:kindmap[string]interface{}


    var spec Spec

    if err := yaml.Unmarshal([]byte(src), &spec); err != nil {

        panic(err)

    }

    tmp, _ := yaml.Marshal(spec.Spec)

    if spec.Kind == "foo" {

        var foo Foo

        yaml.Unmarshal(tmp, &foo)

        fmt.Printf("foo value is %d\n", foo.FooVal)

    }

    if spec.Kind == "bar" {

        tmp, _ := yaml.Marshal(spec.Spec)

        var bar Bar

        yaml.Unmarshal(tmp, &bar)

        fmt.Printf("bar value is %d\n", bar.BarVal)

    }

但它需要额外的步骤并消耗更多的内存(真正的yaml文件可能比示例中更大)。是否存在一些更优雅的方法可以将yaml动态地解构为一组有限的结构?


更新:我正在使用 Yaml 解析器。github.com/go-yaml/yaml v2.1.0


沧海一幻觉
浏览 220回答 2
2回答

开心每一天1111

您可以通过实现自定义函数来执行此操作。但是,对于API的版本,您基本上可以像现在一样做同样的事情,并且只是更好地封装它。UnmarshalYAMLv2但是,如果您切换到使用API,则可以获得更好的效果,实际上允许您在将解析的YAML节点处理为本机Go类型之前对其进行处理。这是它的外观:v3UnmarshalYAMLpackage mainimport (&nbsp; &nbsp; "errors"&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "gopkg.in/yaml.v3")type Spec struct {&nbsp; &nbsp; Kind string&nbsp; &nbsp; &nbsp; `yaml:"kind"`&nbsp; &nbsp; Spec interface{} `yaml:"spec"`}type Foo struct {&nbsp; &nbsp; FooVal int `yaml:"fooVal"`}type Bar struct {&nbsp; &nbsp; BarVal int `yaml:"barVal"`}func (s *Spec) UnmarshalYAML(value *yaml.Node) error {&nbsp; &nbsp; s.Kind = ""&nbsp; &nbsp; for i := 0; i < len(value.Content)/2; i += 2 {&nbsp; &nbsp; &nbsp; &nbsp; if value.Content[i].Kind == yaml.ScalarNode &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value.Content[i].Value == "kind" {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if value.Content[i+1].Kind != yaml.ScalarNode {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return errors.New("kind is not a scalar")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s.Kind = value.Content[i+1].Value&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; if s.Kind == "" {&nbsp; &nbsp; &nbsp; &nbsp; return errors.New("missing field `kind`")&nbsp; &nbsp; }&nbsp; &nbsp; switch s.Kind {&nbsp; &nbsp; case "foo":&nbsp; &nbsp; &nbsp; &nbsp; var foo Foo&nbsp; &nbsp; &nbsp; &nbsp; if err := value.Decode(&foo); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; s.Spec = foo&nbsp; &nbsp; case "bar":&nbsp; &nbsp; &nbsp; &nbsp; var bar Bar&nbsp; &nbsp; &nbsp; &nbsp; if err := value.Decode(&bar); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; s.Spec = bar&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; return errors.New("unknown kind: " + s.Kind)&nbsp; &nbsp; }&nbsp; &nbsp; return nil}var input1 = []byte(`kind: "foo"spec:&nbsp; fooVal: 4`)var input2 = []byte(`kind: "bar"spec:&nbsp; barVal: 5`)func main() {&nbsp; &nbsp; var s1, s2 Spec&nbsp; &nbsp; if err := yaml.Unmarshal(input1, &s1); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; panic(err)&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Printf("Type of spec from input1: %T\n", s1.Spec)&nbsp; &nbsp; if err := yaml.Unmarshal(input2, &s2); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; panic(err)&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Printf("Type of spec from input2: %T\n", s2.Spec)}我建议研究使用YAML标签而不是当前结构的可能性,以便在YAML中对此进行建模;标记正是为此目的而设计的。而不是当前的 YAMLkind: "foo"spec:&nbsp; fooVal: 4你可以写--- !foofooVal: 4现在,您不再需要描述结构了。加载此内容看起来会有所不同,因为您需要一个可以定义的包装根类型,但如果这只是较大结构的一部分,则可能是可行的。您可以在 的字段中访问该标签。kindspecUnmarshalYAML!fooyaml.NodeTag

森林海

要与一起使用,您可以执行以下操作:yaml.v2type yamlNode struct {&nbsp; &nbsp; unmarshal func(interface{}) error}func (n *yamlNode) UnmarshalYAML(unmarshal func(interface{}) error) error {&nbsp; &nbsp; n.unmarshal = unmarshal&nbsp; &nbsp; return nil}type Spec struct {&nbsp; &nbsp; Kind string&nbsp; &nbsp; &nbsp; `yaml:"kind"`&nbsp; &nbsp; Spec interface{} `yaml:"-"`}func (s *Spec) UnmarshalYAML(unmarshal func(interface{}) error) error {&nbsp; &nbsp; type S Spec&nbsp; &nbsp; type T struct {&nbsp; &nbsp; &nbsp; &nbsp; S&nbsp; &nbsp; `yaml:",inline"`&nbsp; &nbsp; &nbsp; &nbsp; Spec yamlNode `yaml:"spec"`&nbsp; &nbsp; }&nbsp; &nbsp; obj := &T{}&nbsp; &nbsp; if err := unmarshal(obj); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; }&nbsp; &nbsp; *s = Spec(obj.S)&nbsp; &nbsp; switch s.Kind {&nbsp; &nbsp; case "foo":&nbsp; &nbsp; &nbsp; &nbsp; s.Spec = new(Foo)&nbsp; &nbsp; case "bar":&nbsp; &nbsp; &nbsp; &nbsp; s.Spec = new(Bar)&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; panic("kind unknown")&nbsp; &nbsp; }&nbsp; &nbsp; return obj.Spec.unmarshal(s.Spec)}https://play.golang.org/p/Ov0cOaedb-x要与一起使用,您可以执行以下操作:yaml.v3type Spec struct {&nbsp; &nbsp; Kind string&nbsp; &nbsp; &nbsp; `yaml:"kind"`&nbsp; &nbsp; Spec interface{} `yaml:"-"`}func (s *Spec) UnmarshalYAML(n *yaml.Node) error {&nbsp; &nbsp; type S Spec&nbsp; &nbsp; type T struct {&nbsp; &nbsp; &nbsp; &nbsp; *S&nbsp; &nbsp;`yaml:",inline"`&nbsp; &nbsp; &nbsp; &nbsp; Spec yaml.Node `yaml:"spec"`&nbsp; &nbsp; }&nbsp; &nbsp; obj := &T{S: (*S)(s)}&nbsp; &nbsp; if err := n.Decode(obj); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; }&nbsp; &nbsp; switch s.Kind {&nbsp; &nbsp; case "foo":&nbsp; &nbsp; &nbsp; &nbsp; s.Spec = new(Foo)&nbsp; &nbsp; case "bar":&nbsp; &nbsp; &nbsp; &nbsp; s.Spec = new(Bar)&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; panic("kind unknown")&nbsp; &nbsp; }&nbsp; &nbsp; return obj.Spec.Decode(s.Spec)}https://play.golang.org/p/ryEuHyU-M2Z
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go