为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
沧海一幻觉 2022-08-15 19:39:23
我有一个文件,其中一个字段可以由一种可能的结构表示。为了简化代码和 yaml 文件,假设我有这些 yaml 文件:yamlkind: "foo"spec:  fooVal: 4kind: "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
查看完整描述

2 回答

?
开心每一天1111

TA贡献1836条经验 获得超13个赞

您可以通过实现自定义函数来执行此操作。但是,对于API的版本,您基本上可以像现在一样做同样的事情,并且只是更好地封装它。UnmarshalYAMLv2


但是,如果您切换到使用API,则可以获得更好的效果,实际上允许您在将解析的YAML节点处理为本机Go类型之前对其进行处理。这是它的外观:v3UnmarshalYAML


package main


import (

    "errors"

    "fmt"

    "gopkg.in/yaml.v3"

)


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"`

}


func (s *Spec) UnmarshalYAML(value *yaml.Node) error {

    s.Kind = ""

    for i := 0; i < len(value.Content)/2; i += 2 {

        if value.Content[i].Kind == yaml.ScalarNode &&

            value.Content[i].Value == "kind" {

            if value.Content[i+1].Kind != yaml.ScalarNode {

                return errors.New("kind is not a scalar")

            }

            s.Kind = value.Content[i+1].Value

            break

        }

    }

    if s.Kind == "" {

        return errors.New("missing field `kind`")

    }

    switch s.Kind {

    case "foo":

        var foo Foo

        if err := value.Decode(&foo); err != nil {

            return err

        }

        s.Spec = foo

    case "bar":

        var bar Bar

        if err := value.Decode(&bar); err != nil {

            return err

        }

        s.Spec = bar

    default:

        return errors.New("unknown kind: " + s.Kind)

    }

    return nil

}


var input1 = []byte(`

kind: "foo"

spec:

  fooVal: 4

`)


var input2 = []byte(`

kind: "bar"

spec:

  barVal: 5

`)


func main() {

    var s1, s2 Spec

    if err := yaml.Unmarshal(input1, &s1); err != nil {

        panic(err)

    }

    fmt.Printf("Type of spec from input1: %T\n", s1.Spec)

    if err := yaml.Unmarshal(input2, &s2); err != nil {

        panic(err)

    }

    fmt.Printf("Type of spec from input2: %T\n", s2.Spec)

}

我建议研究使用YAML标签而不是当前结构的可能性,以便在YAML中对此进行建模;标记正是为此目的而设计的。而不是当前的 YAML


kind: "foo"

spec:

  fooVal: 4

你可以写


--- !foo

fooVal: 4

现在,您不再需要描述结构了。加载此内容看起来会有所不同,因为您需要一个可以定义的包装根类型,但如果这只是较大结构的一部分,则可能是可行的。您可以在 的字段中访问该标签。kindspecUnmarshalYAML!fooyaml.NodeTag


查看完整回答
反对 回复 2022-08-15
?
森林海

TA贡献2011条经验 获得超2个赞

要与一起使用,您可以执行以下操作:yaml.v2


type yamlNode struct {

    unmarshal func(interface{}) error

}


func (n *yamlNode) UnmarshalYAML(unmarshal func(interface{}) error) error {

    n.unmarshal = unmarshal

    return nil

}


type Spec struct {

    Kind string      `yaml:"kind"`

    Spec interface{} `yaml:"-"`

}

func (s *Spec) UnmarshalYAML(unmarshal func(interface{}) error) error {

    type S Spec

    type T struct {

        S    `yaml:",inline"`

        Spec yamlNode `yaml:"spec"`

    }


    obj := &T{}

    if err := unmarshal(obj); err != nil {

        return err

    }

    *s = Spec(obj.S)


    switch s.Kind {

    case "foo":

        s.Spec = new(Foo)

    case "bar":

        s.Spec = new(Bar)

    default:

        panic("kind unknown")

    }

    return obj.Spec.unmarshal(s.Spec)

}

https://play.golang.org/p/Ov0cOaedb-x


要与一起使用,您可以执行以下操作:yaml.v3


type Spec struct {

    Kind string      `yaml:"kind"`

    Spec interface{} `yaml:"-"`

}

func (s *Spec) UnmarshalYAML(n *yaml.Node) error {

    type S Spec

    type T struct {

        *S   `yaml:",inline"`

        Spec yaml.Node `yaml:"spec"`

    }


    obj := &T{S: (*S)(s)}

    if err := n.Decode(obj); err != nil {

        return err

    }


    switch s.Kind {

    case "foo":

        s.Spec = new(Foo)

    case "bar":

        s.Spec = new(Bar)

    default:

        panic("kind unknown")

    }

    return obj.Spec.Decode(s.Spec)

}

https://play.golang.org/p/ryEuHyU-M2Z


查看完整回答
反对 回复 2022-08-15
  • 2 回答
  • 0 关注
  • 162 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信