goccy/go-yaml で yaml の decode/encode
はじめに
goccy/go-yaml というライブラリを使って yaml の decode/encode を行ってみます。
今回は後に openAPI の yaml ファイルを扱うことを想定し anchor/alias を利用したyamlファイルを分割した読み込みと、anchor/aliasを解決した後のyamlファイルの書き出しを行ってみます。
goccy/go-yaml 作者様の情熱溢れる解説記事はQiitaに上がっていました。
私もyamlファイルの分割時に anchor の使い回しをしたくこのライブラリに出会うことができました。
分割したyamlファイルの読み込み
yaml ファイルをまとめて1つのディレクトリに格納しておくと再起的に読み込み、anchor/aliasを解決したのちに decode してくれるようです。
yamlファイルの準備
今回は spec
というディレクトリを作り3つのyamlファイルを格納します。
api.yaml
からchild.yaml
を参照しchild.yaml
からchild
という2階層目のディレクトリ内のgrandchild.yaml
を参照する構成です。
$ tree spec spec ├── api.yaml ├── child │ └── grandchild.yaml └── child.yaml
api.yaml
は yaml.NewDecoder() として読みこむファイルとして以下のように作成します。
a
という構造に&a
という alias をつけてb
という構造から<<: *a
と読み込んでいます。
c
は別ファイルchild.yaml
に定義します。(VSCode上は unidentified alias と怒られますが 問題なく decodeできます)
a: &a aa: 1 ab: hello b: <<: *a ba: 2 c: *c
child.yaml
はapi.yaml
で定義した*c
の実体です。
d
は1階層下の別ファイルgrandchild.yaml
に定義します。
c: &c ca: 3 d: *d
child/grandchild.yaml
は*d
の実体です。
d: &d da: 4
yamlファイルの読み込み
package main import ( "bytes" "fmt" "io/ioutil" "github.com/goccy/go-yaml" "gopkg.in/go-playground/validator.v9" ) type Api struct { A B C } type A struct { AA int AB string } type B struct { *A `yaml:",omitempty,inline"` BA int } type C struct { CA int *D } type D struct { DA int } func main() { // api.yaml の読み込んで*bytes.Bufferへ変換 spec, _ := ioutil.ReadFile("./spec/api.yaml") buf := bytes.NewBuffer(spec) // validation validate := validator.New() dec := yaml.NewDecoder( buf, yaml.RecursiveDir(true), // RecursiveDir(true) とするとReferenceDirs配下のyamlファイルを再起的に読み込んでくれる yaml.ReferenceDirs("spec"), yaml.Validator(validate), ) // 定義した構造体ApiにDecodeする var api Api err := dec.Decode(&api) if err != nil { fmt.Println(err) } fmt.Printf("%+v\n", api) }
実行すると以下のようにspec
ディレクトリ配下のyamlファイルを再起的に読み込み構造体にマッピングできています。
OpenAPIを読み込む時はここの構造体をOpenAPIの仕様とします。
$ go run main.go {A:{AA:1 AB:hello} B:{A:0xc0001f6780 BA:2} C:{CA:3 D:0xc0001eac28}}
anchor/aliasを解決したyamlの書き出し
yamlの書き出しは以下の4行を末尾に追加するだけです。
MergeKeyと呼ばれる<<: *a
といったシンボルは構造体にyaml:",omitempty,inline"
のtagを付与することで解決することができます。
out, err := yaml.Marshal(&api) if err != nil { fmt.Println(err) } fmt.Println(string(out), "\n")
実行すると以下のようなanchor/aliasを解決したyamlを書き出すことができます。
$ go run main.go a: aa: 1 ab: hello b: aa: 1 ab: hello ba: 2 c: ca: 3 d: da: 4
まとめ
goccy/go-yaml というライブラリを使って yaml の decode/encode を行う方法をまとめました。