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生成配置文件
上述示例展示的是使用Decoder直接反序列化*File类型的toml文件。更简单的办法其实是从文件中读取bytes然后直接Unmarshal,可以先使用ioutil.ReadFile()方法获取bytes,这样就省了构造Decoder。go的高版本中os模块已经支持ReadFile()方法,ioutil.ReadFile()依然可用,只不过和os.ReadFile()是一模一样的内容。
二、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)
}