Golang yaml与toml解析

yaml与toml是当前流行度较高的两种配置文件类型,其解析方式也非常类似,因此本文将他们合在一起讲。

go-yaml/yaml: YAML support for the Go language. (github.com)

BurntSushi/toml: TOML parser for Golang with reflection. (github.com)

pelletier/go-toml: Go library for the TOML file format (github.com)

第一个是go yaml解析库,后两个是toml解析库,但BurntSushi的toml解析库已经不再维护,所以推荐使用下面的go-toml,参考:

toml · pkg.go.dev

yaml · pkg.go.dev

一、YAML
package main

import (
	"fmt"
	"log"
	"gopkg.in/yaml.v2"
)

var data = `
a: Easy!
b:
  c: 2
  d: [3, 4]
`

// Note: struct fields must be public in order for unmarshal to correctly populate the data.
type Config struct {
	A string
	B struct {
		RenamedC int   `yaml:"c"`
		D	   []int
	}
}

func main() {
	config := new(Config)

	//unmarshal bytes to config
	err := yaml.Unmarshal([]byte(data), config)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("--- config loads:\n%v\n\n", config)

	//marshal config to bytes
	configBytes, err := yaml.Marshal(config)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- config dumps:\n%s\n\n", string(configBytes))

	m := make(map[interface{}]interface{})

	//unmarshal bytes to map
	err = yaml.Unmarshal([]byte(data), &m)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- map loads:\n%v\n\n", m)

	//marshal map to bytes
	d, err := yaml.Marshal(&m)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- map dumps:\n%s\n\n", string(d))
}

同json标准库相似,yaml.Encoder与yaml.Decoder负责对接文件类型的yaml的读写,我们只需要os.OpenFile创建或打开一个文件,就可以直接写入或读取其中的yaml配置啦:

有如下一个yaml文件:

apiVersion: pingcap.com/v1alpha1
kind: TidbCluster
metadata:
  name: my-test-cluster
  namespace: tidb-cluster
package main

import (
	"fmt"
	"log"
	"gopkg.in/yaml.v2"
	"os"
)

func checkErr(err error){
	if err != nil{
		log.Fatalln(err)
	}
}

type Config struct {
	ApiVersion 	string	`yaml:"apiVersion"`  //yaml解析时会默认把字段解析为全小写来匹配,如果配置文件中使用驼峰规则那么这里就需要使用annotations主动标识
	Kind		string
	Metadata struct{
		Name		string
		Namespace	string
	}
}

func main() {
	config := new(Config)

	readFile, err := os.OpenFile("main.yaml", os.O_RDWR, 0644)
	checkErr(err)
	defer readFile.Close()

	decoder := yaml.NewDecoder(readFile)
	decoder.Decode(config)

	fmt.Printf("apiversion:%s, kind: %s, meta.name:%s, meta.namespace: %s", config.ApiVersion, config.Kind,
		config.Metadata.Name, config.Metadata.Namespace)

	writeFile, err := os.OpenFile("main1.yaml", os.O_CREATE|os.O_RDWR, 0644)
	checkErr(err)
	defer writeFile.Close()

	encoder := yaml.NewEncoder(writeFile)
	defer encoder.Close()
	encoder.Encode(config)
}
//需要特别注意的一点是:
//如果Encoder.Encode(v interface{})的输入不是标准化输入(即struct或map等格式化的输入),而是string,那么写入的文件内容就会变成透传yaml的格式(有一个|2的标志开头),这种生成的yaml文件很难解析,建议如果是从string获取yaml配置,那么先Unmarshal反序列化为标准化格式(map or struct),然后使用此格式Decode生成配置文件

二、TOML解析

toml与yaml以及json的解析也基本一样,不同的是toml库中提供了大量的其他辅助type struct,例如可以不用struct和map来存储yaml的解析结果,我们可以使用toml.Tree类型来存储toml解析结果以方便访问,使用Tree类型的构造方法来获取一个Tree然后使用其type method来访问树的元素即可。

这里先讲述使用传统方式解析toml文件,然后再示例如何使用Tree类型来更便捷的处理toml。

假设我们有一个如下main.toml文件:

[app]
name = "myapp"
[app.log]
max_size = "100MB"
max_files = 3
[app.resource]
cpu = "1000m"
memory = "1GB"
[others]
foo = "foo~"

我们读取toml文件并修改其中max_files的值为4,然后重新写入另一个名为main.toml.new的文件中:

package main

import (
	"fmt"
	"log"
	"os"
	toml "github.com/pelletier/go-toml"
)

func checkErr(err error){
	if err != nil{
		log.Fatalln(err)
	}
}
//目前toml库与yaml相比有一个弱点:yaml在将config struct写入文件时会自动把配置项的首字母重新转回小写,但toml不会,所以为了统一前后格式,这里把toml的annotations都加上
type config struct {
	App struct{
		Name string	`toml:"name"`
		Log struct{
			MaxSize string	`toml:"max_size"`
			MaxFiles uint8	`toml:"max_files"`
		}	`toml:"log"`
		Resource struct{
			Cpu string	`toml:"cpu"`
			Memory string	`toml:"memory"`
		}	`toml:"resource"`
	}	`toml:"app"`
	Others struct{
		Foo string	`toml:"foo"`
	}	`toml:"others"`
}

func main() {
	config := new(config)
	readFile, err := os.OpenFile("main.toml", os.O_RDWR, 0644)
	checkErr(err)
	defer readFile.Close()

	decoder := toml.NewDecoder(readFile)
	decoder.Decode(config)

	fmt.Printf("Current MaxFiles is: %d", config.App.Log.MaxFiles)

	config.App.Log.MaxFiles = 4
	writeFile, err := os.OpenFile("main.toml.new", os.O_CREATE|os.O_RDWR, 0644)
	checkErr(err)
	defer writeFile.Close()

	encoder := toml.NewEncoder(writeFile)
	encoder.Encode(config)
}

一般来说我们可以使用config struct或者一个map[string]interface{}来接收从toml配置文件或toml string中的配置,但是两者都有一些缺陷:使用config struct需要预先定义好包含详细列信息的struct(使用interface{}代替亦可不过那和直接使用map没什么区别了),使用map[string]interface{}那么在获取配置值时需要大量的使用类型断言。

那么有没有一种类似python语言中那种直接得到一个dict类型然后直接获得配置值的方式?

go-toml包提供了Tree类型来实现这种需求,他有些类似于使用map[string]interface{}但是不再需要那么多层的断言了,而且包含了很多便捷的method,直接示例:

package main

import (
	"fmt"
	"log"
	"os"
	toml "github.com/pelletier/go-toml"
)

func checkErr(err error){
	if err != nil{
		log.Fatalln(err)
	}
}

func main() {
	configTree,err := toml.LoadFile("main.toml")
	checkErr(err)
	maxFiles := configTree.Get("app.log.max_files").(int64) //坑1:数字被默认解析为int64类型,所以必须断言为int64
	fmt.Printf("Current MaxFiles is: %d", maxFiles)

	writeFile,err := os.OpenFile("main.toml.new", os.O_CREATE|os.O_RDWR, 0644)
	checkErr(err)
	defer writeFile.Close()

	var newMaxFiles int64 = 4 //坑2:定义为int64,原因同上
	configTree.Set("app.log.max_files", newMaxFiles)
	_,err = configTree.WriteTo(writeFile) //小改进:写入的toml文件中所有key都和原来一样是小写的,不用再担心大小写的问题,使用map处理时是否会有大小写问题懒的测啦
	checkErr(err)
}

  

上一篇:C# 的 TOML 库


下一篇:NGUI(六)InputField输入框