如何在golang中解析保留注释的通用yaml?

7

我正在使用golang yaml v3库进行开发。目标是从带有注释的文件中解析任何yaml(即没有预定义的结构),能够设置或取消结果树中的任何值,并将其写回文件。

然而,我遇到了一个相当奇怪的问题。如下面的代码所示,如果传递给Unmarshal函数的主类型是interface{},则不会保留注释,库使用映射和切片来表示yaml的结构。另一方面,如果我使用(在这种情况下)[]yaml.Node结构,则会将所有节点作为yaml.Node[]yaml.Node内部表示。这更或多或少是我想要的,因为它允许保留注释。但是,它不是一个通用的解决方案,因为至少有两种不同的场景- YAML以数组或映射开头,我不确定如何优雅地处理这两种情况。

请问你能否指点我正确的方向,并详细说明为什么库会有这种行为?

package main

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

type Document interface{} // change this to []yaml.Node and it will work with comments // change it to yaml.Node and it will not work

var data string = ` # Employee records
-  martin:
    name: Martin D'vloper
    job: Developer
    skills:
      - python
      - perl
      - pascal
-  tabitha:
    name: Tabitha Bitumen
    job: Developer
    skills:
      - lisp
      - fortran
      - erlang
`

func toSlice(slice interface{}) []interface{} {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("InterfaceSlice() given a non-slice type")
    }

    ret := make([]interface{}, s.Len())

    for i:=0; i<s.Len(); i++ {
        ret[i] = s.Index(i).Interface()
    }

    return ret
}

func main() {
    var d Document
    err := yaml.Unmarshal([]byte(data), &d)
    if err != nil {
        panic(err)
    }

    slice := toSlice(d)
    fmt.Println(reflect.ValueOf(slice[0]).Kind())

    fmt.Println(reflect.TypeOf(d))
    fmt.Println(reflect.ValueOf(d).Kind())
    output, err := yaml.Marshal(&d)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(output))

}
2个回答

6
另一方面,如果我使用(在这种情况下)[]yaml.Node结构,则它会将所有节点内部表示为yaml.Node或[]yaml.Node。这并不准确。go-yaml允许您将结构的任何子树保留为yaml.Node,以便稍后进行处理。在此节点内部,所有内容都表示为yaml.Node,并且作为集合(序列或映射)的节点恰好将其子节点存储为[]yaml.Node。但是没有节点直接表示为[]yaml.Node。当您反序列化为[]yaml.Node时,您将顶级节点反序列化为本地结构(一个切片),同时保留子节点未构造(将YAML节点加载到本地结构中的过程在规范中称为“构造”)。go-yaml实际上不支持。
type Document yaml.Node

但是如果你只是这样做

var d yaml.Node

评论也将被保留(显然,toSlice 将不再起作用):

- # Employee records
  martin:
      name: Martin D'vloper
      job: Developer
      skills:
        - python
        - perl
        - pascal
- tabitha:
      name: Tabitha Bitumen
      job: Developer
      skills:
        - lisp
        - fortran
        - erlang

现在我们可以看到,注释的位置是不同的。这是因为go-yaml只是存储在表示列表项的yaml.Node中的信息,即"在这个列表项之前有一个注释"。关于注释实际上位于哪里的信息已经丢失了。您应该感激有任何关于注释的信息,因为大多数YAML实现会在更早的阶段删除它们,因为规范规定注释不得传达内容信息。
您可能希望阅读我想加载一个YAML文件,可能编辑数据,然后再次转储。如何保留格式?,其中详细介绍了在加载YAML文件期间为什么、何时以及如何丢失信息。简而言之:如果您的目标是加载YAML文件并备份所有格式,那么这是不可能的(除非基本上自己进行解析),如果这是您的目标,那么YAML不是适合您的工具。

我受到了这个项目的严重打击... - undefined

1
当 go-yaml 解析 YAML 文档时,它总是首先创建一个 YAML 节点树。它是否将该节点树转换为纯 Golang 对象取决于传递给 `Unmarshall` 的 `out` 参数的类型。以下是来自 go-yaml 源代码的代码片段:
func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
    // ...
    if out.Type() == nodeType {
        out.Set(reflect.ValueOf(n).Elem())
        return true
    }
    // ...
}

基本上,如果提供的参数是指向yaml.Node的指针,则go-yaml会跳过节点树的转换。当您的参数类型为interface{}或任何其他不是yaml.Node的类型时,它将进行转换。

为了保留注释并允许在顶层使用映射、数组或甚至单个值,只需将*yaml.Node作为第二个参数传递给yaml.Unmarshal

var n yaml.Node
err := yaml.Unmarshal(bytes, &n)

如果数组位于顶层,则根节点将包含作为其子节点的数组元素的YAML节点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接