【问题标题】:Unmarshalling YAML to ordered maps将 YAML 解组为有序映射
【发布时间】:2020-08-04 20:34:07
【问题描述】:

我正在尝试使用 Go YAML v3 解组以下 YAML。

model:
  name: mymodel
  default-children:
  - payment

  pipeline:
    accumulator_v1:
      by-type:
        type: static
        value: false
      result-type:
        type: static
        value: 3

    item_v1:
      amount:
        type: schema-path
        value: amount
      start-date:
        type: schema-path
        value: start-date

在管道下是任意数量的有序项目。应该将其解组的结构如下所示:

type PipelineItemOption struct {
        Type string
        Value interface{}
}

type PipelineItem struct {
        Options map[string]PipelineItemOption
}

type Model struct {
        Name string
        DefaultChildren []string `yaml:"default-children"`
        Pipeline orderedmap[string]PipelineItem    // "pseudo code"
}

这如何与 Golang YAML v3 一起使用?在 v2 中有 MapSlice,但在 v3 中没有了。

【问题讨论】:

  • 为什么不使用appendpipeline 中的每个项目添加到Pipelines 的数组中?
  • 但是怎么样,真的吗?我可以首先解组到 yaml.Node,然后遍历子项以选择每个单独的属性并将其设置在模型上,然后手动遍历 yaml.Node 的管道,......这将是高度非通用的,我会要么需要对每个属性进行硬编码,要么使用反射——这只适用于可寻址字段。没有任何方法可以保留不涉及编写高度特定的解析器的顺序吗?

标签: go yaml


【解决方案1】:

您声称编组到中间 yaml.Node 是高度非通用的,但我真的不明白为什么。它看起来像这样:

package main

import (
    "fmt"
    "gopkg.in/yaml.v3"
)

type PipelineItemOption struct {
        Type string
        Value interface{}
}

type PipelineItem struct {
    Name string
        Options map[string]PipelineItemOption
}

type Pipeline []PipelineItem

type Model struct {
        Name string
        DefaultChildren []string `yaml:"default-children"`
        Pipeline Pipeline
}

func (p *Pipeline) UnmarshalYAML(value *yaml.Node) error {
    if value.Kind != yaml.MappingNode {
        return fmt.Errorf("pipeline must contain YAML mapping, has %v", value.Kind)
    }
    *p = make([]PipelineItem, len(value.Content)/2)
    for i := 0; i < len(value.Content); i += 2 {
        var res = &(*p)[i/2]
        if err := value.Content[i].Decode(&res.Name); err != nil {
            return err
        }
        if err := value.Content[i+1].Decode(&res.Options); err != nil {
            return err
        }
    }
    return nil
}


var input []byte = []byte(`
model:
  name: mymodel
  default-children:
  - payment

  pipeline:
    accumulator_v1:
      by-type:
        type: static
        value: false
      result-type:
        type: static
        value: 3

    item_v1:
      amount:
        type: schema-path
        value: amount
      start-date:
        type: schema-path
        value: start-date`)

func main() {
    var f struct {
        Model Model
    }
    var err error
    if err = yaml.Unmarshal(input, &f); err != nil {
        panic(err)
    }
    fmt.Printf("%v", f)
}

【讨论】:

  • 谢谢,这行得通,只是非通用部分是现在每当您更改结构时,您也必须更改主函数,因为它直接引用每个结构字段(两次)。
  • @knipknap 想想,这实际上是不必要的。我编辑了代码,使Model 的结构不再与解包管道相关
【解决方案2】:

对我来说,要弄清楚 v3 期望什么而不是 MapSlice 是一个学习曲线。与@flyx 的回答类似,yaml.Node 树需要被遍历,尤其是它的[]Content

这是一个提供有序map[string]interface{} 的实用程序,它更易于重用和整洁。 (虽然它不像指定的问题那样受到限制。)

根据上面的结构,重新定义Pipeline

type Model struct {
    Name string
    DefaultChildren []string `yaml:"default-children"`
    Pipeline *yaml.Node
}

使用实用程序 fn 遍历yaml.Node 内容:

// fragment
var model Model
if err := yaml.Unmarshal(&model) ; err != nil {
    return err
}

om, err := getOrderedMap(model.Pipeline)
if err != nil {
    return err
}

for _,k := range om.Order {
    v := om.Map[k]
    fmt.Printf("%s=%v\n", k, v)
}

实用程序 fn:

type OrderedMap struct {
    Map map[string]interface{}
    Order []string
}

func getOrderedMap(node *yaml.Node) (om *OrderedMap, err error) {
    content := node.Content
    end := len(content)
    count := end / 2

    om = &OrderedMap{
        Map: make(map[string]interface{}, count),
        Order: make([]string, 0, count),
    }
    
    for pos := 0 ; pos < end ; pos += 2 {
        keyNode := content[pos]
        valueNode := content[pos + 1]

        if keyNode.Tag != "!!str" {
            err = fmt.Errorf("expected a string key but got %s on line %d", keyNode.Tag, keyNode.Line)
            return
        }

        var k string
        if err = keyNode.Decode(&k) ; err != nil {
            return
        }

        var v interface{}
        if err = valueNode.Decode(&v) ; err != nil {
            return
        }

        om.Map[k] = v
        om.Order = append(om.Order, k)
    }

    return
}

【讨论】:

    猜你喜欢
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 2019-07-08
    • 1970-01-01
    • 2012-11-12
    • 2020-09-24
    • 2019-01-08
    • 1970-01-01
    相关资源
    最近更新 更多