您现在的位置是:首页 >技术教程 >golang yaml 解析问题网站首页技术教程

golang yaml 解析问题

天文学 2024-06-17 11:27:51
简介golang yaml 解析问题

golang 中解析 yaml 格式内容可以使用 yaml.v3 库来解决。下载 go 依赖

go get -u gopkg.in/yaml.v3

1. 示例 yaml 数据

config_mail_template:
  description: 验证码
  one: Verification Code
  other: Verification Code

config_mail_template_reset_code:
  description: 重置密码
  one: Reset password
  other: Reset password
  
# 注释内容1
config_custom_tag: # 注释内容2
  description: 自定义
  one: Custom Tag
  other: Custom Tag

#注释内容3

2. 普通解析

普通解析流程,解析到 map 对象,会失去对 key 的定义顺序

package yaml_demo

import (
	"os"
	"testing"

	"gopkg.in/yaml.v3"
)

type Asset struct {
	Description string `yaml:"description"`
	One         string `yaml:"one"`
	Other       string `yaml:"other"`
}

func TestParseNormal(t *testing.T) {
	file, err := os.ReadFile("data.yaml")
	if err != nil {
		t.Error(err.Error())
		return
	}

	var assets map[string]Asset
	err = yaml.Unmarshal(file, &assets)
	if err != nil {
		t.Error(err.Error())
		return
	}
	t.Log(len(assets))
}

3. 顺序解析 yaml 中的 key

yaml3 定义了 yaml.Node 对象
第一种方式
可以通过实现 UnmarshalYAML() 接口,来实现自定义对象的解析,并且保证解析 key 的顺序性

第二种方式
直接将 bytes 解析到 yaml.Node 中,此时 yaml.Node 就是文档对象

var node yaml.Node
yaml.Unmarshal(bytes,&node)
package yaml_demo

import (
	"gopkg.in/yaml.v3"
	"os"
	"testing"
)

type Item struct {
	Name        string
	Description string `yaml:"description"`
	One         string `yaml:"one"`
	Other       string `yaml:"other"`
}

type Items []Item

// UnmarshalYAML 自定义解析
func (a *Items) UnmarshalYAML(value *yaml.Node) error {
	for i := 0; i < len(value.Content); i += 2 {
		var item Item
		if err := value.Content[i+1].Decode(&item); err != nil {
			return err
		}
		item.Name = value.Content[i].Value

		*a = append(*a, item)
	}
	return nil
}

func TestParseToSlice(t *testing.T) {
	bytes, err := os.ReadFile("data.yaml")
	if err != nil {
		t.Error(err.Error())
		return
	}

	var items Items
	err = yaml.Unmarshal(bytes, &items)
	if err != nil {
		t.Error(err.Error())
		return
	}

	t.Log(len(items))
}

4. 顺序解析后回写问题

yaml 中的 node 在更新完成后,回写内容时,虽然保留了注释,但是会去掉空白行
为了保证和原来的文件相同的空白行和注释内容,可以对 yaml 内容做如下处理

  1. 将 yaml 文件读取到 bytes.Buffer 中,并对其中的空白行处理,使用占位符替代空白??行,例如使用 “#placehold” 字符串,因为 “#” 是 yaml 中的注释,所以对文件内容本身没有影响
  2. 将 bytes.Buffer 中的内容解析成 yaml.Node 对象,并对其中需要更新的内容进行更新
  3. 回写时,首先使用 yaml.Marshal 将 yaml.Node 对象转换成 bytes.Buffer,然后将占位符再替换回换行符,写入文件
// 加载 yaml 文件,将空白行使用占位符替换
func loadYamlNode(file string) (*yaml.Node, error) {
	dataBytes, err := os.ReadFile(file)
	if err != nil {
		return nil, err
	}

	buffer := bytes.NewBuffer(dataBytes)
	storeBytes := make([]byte, 0, 2*buffer.Len())
	storeBuffer := bytes.NewBuffer(storeBytes)

	for {
		line, err := buffer.ReadString('
')
		if err != nil && err == io.EOF {
			break
		}
		if line != "
" {
			storeBuffer.WriteString(line)
		} else {
			storeBuffer.WriteString("#placehold
")
		}
	}

	var dataNode yaml.Node
	err = yaml.Unmarshal(storeBuffer.Bytes(), &dataNode)
	if err != nil {
		return nil, err
	}
	return &dataNode, nil
}

// 回写更新内容,将占位符使用空白行替换
func saveUpdatedContent(docNode *yaml.Node, file string) error {
	var bytesData []byte
	buffer := bytes.NewBuffer(bytesData)
	encoder := yaml.NewEncoder(buffer)
	encoder.SetIndent(2)
	err := encoder.Encode(docNode)
	if err != nil {
		return err
	}

	store := make([]byte, 0, buffer.Len())
	storeBuffer := bytes.NewBuffer(store)

	for {
		line, err := buffer.ReadString('
')
		if err != nil && err == io.EOF {
			break
		}
		if line == "#placehold
" {
			storeBuffer.WriteString("
")
		} else {
			storeBuffer.WriteString(line)
		}
	}

	err = os.WriteFile(file, storeBuffer.Bytes(), 0666)
	return err
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。