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,参考:
一、YAMLpackage 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) }