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生成配置文件

上述示例展示的是使用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)
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM