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

如何在保留注释的情况下解析 golang 中的一般 yaml?

如何在保留注释的情况下解析 golang 中的一般 yaml?

Go
拉丁的传说 2022-06-06 16:00:37
我正在玩 golang yaml v3 库。目标是从带有注释的文件中解析任何 yaml(这意味着我没有预定义的结构),能够在结果树中设置或取消设置任何值并将其写回文件。但是,我遇到了相当奇怪的行为。正如您在下面的代码中看到的,如果传递给 Unmarshal 函数的主要类型是interface{},则不会保留任何注释,并且库使用映射和切片来表示 yaml 的结构。另一方面,如果我使用(在这种情况下)[]yaml.Node结构,它确实在内部将所有节点表示为yaml.Nodeor []yaml.Node。这或多或少是我想要的,因为它允许保留评论。然而,这不是一个通用的解决方案,因为至少有两种不同的场景——YAML 以数组或地图开头,我不确定如何优雅地处理这两种情况。您能否指出我正确的方向并详细说明图书馆为什么会这样?package mainimport (    "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 workvar 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 回答

?
扬帆大鱼

TA贡献1799条经验 获得超9个赞

另一方面,如果我使用(在这种情况下)[]yaml.Node 结构,它确实在内部将所有节点表示为 yaml.Node 或 []yaml.Node。


这是不准确的。go-yaml 允许您保留结构的任何子树以yaml.Node供以后处理。在这个节点内,一切都表示为yaml.Node,而作为集合(序列或映射)的节点恰好将其子节点存储为[]yaml.Node。但没有节点直接表示为[]yaml.Node。


当您反序列化 into[]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 文件期间信息丢失的原因、时间和方式。TL; DR:不可能(基本上不自己解析)加载 YAML 文件并将其转储回同时保留所有格式,如果这是您的目标,那么 YAML 对您来说是错误的工具。


查看完整回答
反对 回复 2022-06-06
?
哆啦的时光机

TA贡献1779条经验 获得超6个赞

go-yaml 解析 YAML 文档时,总是先创建 YAML 节点树。然后它是否将该节点树转换为普通的 Golang 对象,取决于out传递给Unmarshall. 这是来自 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. 当您的参数类型是interface{}或以外的任何其他内容yaml.Node时,它将进行转换。


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


var n yaml.Node

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

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


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

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号