我最近看到GO yaml库有了新版本(V3)

具有nodes个功能(在我看来这是一个杀手级功能:),它可以在不更改文件 struct 的情况下帮助很多人修改yaml

但由于它是相当新的(从上周开始),我没有找到一些对context which I need有帮助的文档和示例(添加新的对象/ node 并保留file structure the same而不删除注释等).

我需要的是操作YAML文件

例如

假设我有这个YAML文件

version: 1
type: verbose
kind : bfr

# my list of applications
applications:
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test

现在我得到了一个json对象(例如,带有app2),我需要将其插入到现有文件中

[

    {
        "comment: "Second app",
        "name": "app2",
        "kind": "golang",
        "path": "app2",
        "exec": {
            "platforms": "dockerh",
            "builder": "test"
        }
    }
]

并且我需要在第一个应用程序之后将其添加到YML文件中(Applications是应用程序数组)

version: 1
type: verbose
kind : bfr

# my list of applications
applications:

#  First app
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test

# Second app
  - name: app2
    kind: golang
    path: app2
    exec:
      platforms: dockerh
      builder: test

可以从YAML文件添加新的json对象吗?同时删除现有的

I also found this blog https://blog.ubuntu.com/2019/04/05/api-v3-of-the-yaml-package-for-go-is-available

这是表示对象的类型

type VTS struct {
    version string       `yaml:"version"`
    types   string       `yaml:"type"`
    kind    string       `yaml:"kind,omitempty"`
    apps    Applications `yaml:"applications,omitempty"`
}

type Applications []struct {
    Name string `yaml:"name,omitempty"`
    Kind string `yaml:"kind,omitempty"`
    Path string `yaml:"path,omitempty"`
    Exec struct {
        Platforms string `yaml:"platforms,omitempty"`
        Builder   string `yaml:"builder,omitempty"`
    } `yaml:"exec,omitempty"`
}

update

在测试由wiil7200提供的解决方案后,我发现2个问题

我在最后用了,把它写到文件里 err = ioutil.WriteFile("output.yaml", b, 0644)

yaml输出有两个问题.

  1. 应用程序的数组是从注释开始的,它应该

  2. name个条目之后,kind属性和之后的所有其他属性都是

你知道如何解决这些问题吗?说到comments这个问题,比方说我是从别的地方弄来的 而不是来自json(如果这会让它更简单的话).

version: 1
type: verbose
kind: bfr


# my list of applications
applications:
-   #  First app
name: app1
    kind: nodejs
    path: app1
    exec:
        platforms: k8s
        builder: test
-   # test 1
name: app2
    kind: golang
    path: app2
    exec:
        platform: dockerh
        builder: test

推荐答案

首先,让我从使用yaml开始.当从有效的yaml封送时, node 不会生成有效的yaml,如下例所示.可能应该提出一个问题.

package main

import (
    "fmt"
    "log"

    "gopkg.in/yaml.v3"
)

var (
    sourceYaml = `version: 1
type: verbose
kind : bfr

# my list of applications
applications:

#  First app
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test
`
)

func main() {
    t := yaml.Node{}

    err := yaml.Unmarshal([]byte(sourceYaml), &t)
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    b, err := yaml.Marshal(&t)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(b))
}

在go版本go1中生成以下无效yaml.12.3 windows/amd64

version: 1
type: verbose
kind: bfr


# my list of applications
applications:
-   #  First app
name: app1
    kind: nodejs
    path: app1
    exec:
        platforms: k8s
        builder: test

其次,使用像这样的 struct

type VTS struct {
    Version string       `yaml:"version" json:"version"`
    Types   string       `yaml:"type" json:"type"`
    Kind    string       `yaml:"kind,omitempty" json:"kind,omitempty"`
    Apps    yaml.Node `yaml:"applications,omitempty" json:"applications,omitempty"`
}

从ubuntu的博客和源代码文档来看,它似乎可以正确识别 struct 中作为 node 的字段,并单独构建树,但事实并非如此.

version: "1"
type: verbose
kind: bfr
applications:
    kind: 2
    style: 0
    tag: '!!seq'
    value: ""
    anchor: ""
    alias: null
    content:
    -   #  First app
name: app1
        kind: nodejs
        path: app1
        exec:
            platforms: k8s
            builder: test
    headcomment: ""
    linecomment: ""
    footcomment: ""
    line: 9
    column: 3

忽略了yaml的第一个问题和元帅错误. struct 中的 node (在gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467上),我们现在可以开始操作包公开的 node .不幸的是,没有一种抽象可以轻松地添加 node ,因此使用可能会有所不同,识别 node 可能会很麻烦.反思在这里可能会有所帮助,所以我把它留给你们作为练习.

您将发现Comment spew.Dumps以一种很好的格式转储整个 node 树,这有助于在向源树添加 node 时进行调试.

当然,您也可以删除 node ,您只需要确定需要删除哪些特定 node .如果是映射或序列,则只需确保删除父 node 即可.

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "gopkg.in/yaml.v3"
)

var (
    sourceYaml = `version: 1
type: verbose
kind : bfr

# my list of applications
applications:

#  First app
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test
`
    modifyJsonSource = `
[

    {
        "comment": "Second app",
        "name": "app2",
        "kind": "golang",
        "path": "app2",
        "exec": {
            "platforms": "dockerh",
            "builder": "test"
        }
    }
]
`
)

// VTS Need to Make Fields Public otherwise unmarshalling will not fill in the unexported fields.
type VTS struct {
    Version string       `yaml:"version" json:"version"`
    Types   string       `yaml:"type" json:"type"`
    Kind    string       `yaml:"kind,omitempty" json:"kind,omitempty"`
    Apps    Applications `yaml:"applications,omitempty" json:"applications,omitempty"`
}

type Applications []struct {
    Name string `yaml:"name,omitempty" json:"name,omitempty"`
    Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
    Path string `yaml:"path,omitempty" json:"path,omitempty"`
    Exec struct {
        Platforms string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
        Builder   string `yaml:"builder,omitempty" json:"builder,omitempty"`
    } `yaml:"exec,omitempty" json:"exec,omitempty"`
    Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}

func main() {
    t := yaml.Node{}

    err := yaml.Unmarshal([]byte(sourceYaml), &t)
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    // Look for the Map Node with the seq array of items
    applicationNode := iterateNode(&t, "applications")

    // spew.Dump(iterateNode(&t, "applications"))

    var addFromJson Applications
    err = json.Unmarshal([]byte(modifyJsonSource), &addFromJson)
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    // Delete the Original Applications the following options:
    // applicationNode.Content = []*yaml.Node{}
    // deleteAllContents(applicationNode)
    deleteApplication(applicationNode, "name", "app1")


    for _, app := range addFromJson {
        // Build New Map Node for new sequences coming in from json
        mapNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}

        // Build Name, Kind, and Path Nodes
        mapNode.Content = append(mapNode.Content, buildStringNodes("name", app.Name, app.Comment)...)
        mapNode.Content = append(mapNode.Content, buildStringNodes("kind", app.Kind, "")...)
        mapNode.Content = append(mapNode.Content, buildStringNodes("path", app.Path, "")...)

        // Build the Exec Nodes and the Platform and Builder Nodes within it
        keyMapNode, keyMapValuesNode := buildMapNodes("exec")
        keyMapValuesNode.Content = append(keyMapValuesNode.Content, buildStringNodes("platform", app.Exec.Platforms, "")...)
        keyMapValuesNode.Content = append(keyMapValuesNode.Content, buildStringNodes("builder", app.Exec.Builder, "")...)

        // Add to parent map Node
        mapNode.Content = append(mapNode.Content, keyMapNode, keyMapValuesNode)

        // Add to applications Node
        applicationNode.Content = append(applicationNode.Content, mapNode)
    }
    // spew.Dump(t)
    b, err := yaml.Marshal(&t)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(b))
}

// iterateNode will recursive look for the node following the identifier Node,
// as go-yaml has a node for the key and the value itself
// we want to manipulate the value Node
func iterateNode(node *yaml.Node, identifier string) *yaml.Node {
    returnNode := false
    for _, n := range node.Content {
        if n.Value == identifier {
            returnNode = true
            continue
        }
        if returnNode {
            return n
        }
        if len(n.Content) > 0 {
            ac_node := iterateNode(n, identifier)
            if ac_node != nil {
                return ac_node
            }
        }
    }
    return nil
}

// deleteAllContents will remove all the contents of a node
// Mark sure to pass the correct node in otherwise bad things will happen
func deleteAllContents(node *yaml.Node) {
    node.Content = []*yaml.Node{}
}

// deleteApplication expects that a sequence Node with all the applications are present
// if the key value are not found it will not log any errors, and return silently
// this is expecting a map like structure for the applications
func deleteApplication(node *yaml.Node, key, value string) {
    state := -1
    indexRemove := -1
    for index, parentNode := range node.Content {
        for _, childNode := range parentNode.Content {
            if key == childNode.Value && state == -1 {
                state += 1
                continue // found expected move onto next
            }
            if value == childNode.Value && state == 0 {
                state += 1
                indexRemove = index
                break // found the target exit out of the loop
            } else if state == 0 {
                state = -1
            }
        }
    }
    if state == 1 {
        // Remove node from contents
        // node.Content = append(node.Content[:indexRemove], node.Content[indexRemove+1:]...)
        // Don't Do this you might have a potential memory leak source: https://github.com/golang/go/wiki/SliceTricks
        // Since the underlying nodes are pointers
        length := len(node.Content)
        copy(node.Content[indexRemove:], node.Content[indexRemove+1:])
        node.Content[length-1] = nil
        node.Content = node.Content[:length-1]
    }
}


// buildStringNodes builds Nodes for a single key: value instance
func buildStringNodes(key, value, comment string) []*yaml.Node {
    keyNode := &yaml.Node{
        Kind:        yaml.ScalarNode,
        Tag:         "!!str",
        Value:       key,
        HeadComment: comment,
    }
    valueNode := &yaml.Node{
        Kind:  yaml.ScalarNode,
        Tag:   "!!str",
        Value: value,
    }
    return []*yaml.Node{keyNode, valueNode}
}

// buildMapNodes builds Nodes for a key: map instance
func buildMapNodes(key string) (*yaml.Node, *yaml.Node) {
    n1, n2 := &yaml.Node{
        Kind:  yaml.ScalarNode,
        Tag:   "!!str",
        Value: key,
    }, &yaml.Node{Kind: yaml.MappingNode,
        Tag: "!!map",
    }
    return n1, n2
}

生成YAML

version: 1
type: verbose
kind: bfr


# my list of applications
applications:
-   #  First app
name: app1
    kind: nodejs
    path: app1
    exec:
        platforms: k8s
        builder: test
-   # Second app
name: app2
    kind: golang
    path: app2
    exec:
        platform: dockerh
        builder: test

Go相关问答推荐

gorm如何声明未自动更新的unix时间戳米尔斯字段

如何从google.golang.org/grpc/stats包中将golang中不同事件的输出进行组合,以获取func HandlePRC

运行add. inf,这样我们就可以在app.conf中使用. inf参数了?

../golang/pkg/mod/github.com/wmentor/lemmas@v0.0.6/processor.go:72:9:未定义:令牌.进程

未对GoFr中的所有请求进行跟踪

如何在Golang中获取mp3文件的持续时间?

Golang在不写入磁盘的情况下为jpeg图像生成一致的哈希

Azure golang SDK - 将 AcrPull 角色分配给 AKS 群集

无法读取postman 中的表单数据

为什么我只收到部分错误而不是我启动的 goroutines 的所有错误?

GoLang: gocui 边框 colored颜色

如何将 npm 安装进度条通过管道传输到终端?

获取切片元素的地址是否意味着 Go 中元素的副本?

致命错误:找不到由 zergon321/reisen 引起的libavcodec/avcodec.h文件

如何将多个切片打印为一个切片?

将 Golang Gin 与 AWS Lambda 和无服务器与代理路径一起使用

具有相同提前返回语句的函数的不同基准测试结果

go 堆栈跟踪:在某些函数调用参数或返回值之后的问题(?)标记是什么意思?

将 Simple Go Web 应用程序部署到 Elastic Beanstalk

Go 错误:cannot use generic type without instantiation