首先,让我说一下使用yaml.Node时,在从有效的yaml解组后进行编组不会产生有效的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.12.3 windows/amd64中,会产生以下无效的yaml。
version: 1
type: verbose
kind: bfr
applications:
-
name: app1
kind: nodejs
path: app1
exec:
platforms: k8s
builder: test
其次,使用结构体如下:
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的博客和源文件文档来看,它似乎可以正确识别结构体中的节点字段并单独构建该树形结构,但事实并非如此。当解组后,它将给出一个正确的节点树,但重新组合后,它将生成以下YAML,其中包含所有yaml.Node公开的字段。遗憾的是,我们不能走这条路,必须找到另一种方法。
version: "1"
type: verbose
kind: bfr
applications:
kind: 2
style: 0
tag: '!!seq'
value: ""
anchor: ""
alias: null
content:
-
name: app1
kind: nodejs
path: app1
exec:
platforms: k8s
builder: test
headcomment: ""
linecomment: ""
footcomment: ""
line: 9
column: 3
忽略掉gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467中yaml.Nodes在结构体中的第一个问题和marshal bug,我们现在可以开始操作该包暴露出来的Nodes。不幸的是,没有一个抽象层级能够轻松添加Nodes,因此使用可能会有所不同,而且识别节点可能会很麻烦。这里反射可能会有所帮助,所以我将其留给您作为一项练习。
您将找到注释spew.Dumps,它以良好的格式转储整个节点树,这在向源树添加节点时有助于调试。
当然,您也可以删除节点,只需确定需要删除哪些特定节点即可。您只需确保如果它是一个map或sequence,则删除父节点。
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"
}
}
]
`
)
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)
}
applicationNode := iterateNode(&t, "applications")
var addFromJson Applications
err = json.Unmarshal([]byte(modifyJsonSource), &addFromJson)
if err != nil {
log.Fatalf("error: %v", err)
}
deleteApplication(applicationNode, "name", "app1")
for _, app := range addFromJson {
mapNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
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, "")...)
keyMapNode, keyMapValuesNode := buildMapNodes("exec")
keyMapValuesNode.Content = append(keyMapValuesNode.Content, buildStringNodes("platform", app.Exec.Platforms, "")...)
keyMapValuesNode.Content = append(keyMapValuesNode.Content, buildStringNodes("builder", app.Exec.Builder, "")...)
mapNode.Content = append(mapNode.Content, keyMapNode, keyMapValuesNode)
applicationNode.Content = append(applicationNode.Content, mapNode)
}
b, err := yaml.Marshal(&t)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}
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
}
func deleteAllContents(node *yaml.Node) {
node.Content = []*yaml.Node{}
}
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
}
if value == childNode.Value && state == 0 {
state += 1
indexRemove = index
break
} else if state == 0 {
state = -1
}
}
}
if state == 1 {
length := len(node.Content)
copy(node.Content[indexRemove:], node.Content[indexRemove+1:])
node.Content[length-1] = nil
node.Content = node.Content[:length-1]
}
}
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}
}
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
applications:
-
name: app1
kind: nodejs
path: app1
exec:
platforms: k8s
builder: test
-
name: app2
kind: golang
path: app2
exec:
platform: dockerh
builder: test