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,參考:
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)
}
