概要
golangでyamlをラフに扱うパッケージを作りました。
xshoji/go-rough-yaml: go-rough-yaml provide accessor methods to edit yaml.
https://github.com/xshoji/go-rough-yaml
背景とかいいから使いっぷり教えてよ!って方は使い方までどうぞ
背景
yamlをgolangで参照、加工しようとした際、お手軽にyamlのデータを扱えるパッケージが存在しないことに気づきました。
ここでいう「お手軽」とは、例えば特定のキーを参照、削除、あるいは書き換えたりするだけの操作を指します。
goでyamlを扱うためには
go-yaml/yaml: YAML support for the Go language.
https://github.com/go-yaml/yaml
を使うのが一般的です。yamlの内容を構造体にUnmarshalして利用しますが、構造体を定義するのが面倒です。
このとき構造体を定義せずとも、map[interface{}]interface{}
という最強の型なしmapにUnmarshalすることで、
ある程度お手軽にyamlのデータを参照、加工できるのですが1点大きな問題があります。それは、
- yamlに戻したときにmap構造のキー順が保証されない(勝手にソートされる)
ということです。
bbb:
c: 1
b: 2
aaa:
f: 3
e: 4
というyamlをmapにUnmarshalして、そのままMarshalでyamlに戻すと
aaa:
e: 4
f: 3
bbb:
b: 2
c: 1
という感じで勝手にキーでソートされてしまいます。これは嫌です。
なぜなら、見やすいように意図的に整理しているyaml内のデータが勝手にソートされたら困りますし、
加工せずMarshalしたyamlであっても一切変更がないのに差分だらけになります。
この問題に対して、yaml.MapSlice
という便利な構造体が用意されていて、これは上述の課題を解決するものです。
この構造体を使うことで、map構造の順序を保持したままyamlを加工できます。
が、mapではなくsliceとしてデータを扱うため、mapのキーバリュー構造のように簡単に目的の値にアクセスできません。
また、値の加工もかなり面倒です。
そこで、このyaml.MapSlice
の特性(map構造の順序を保持する)を活かしつつ、
キーバリュー構造のようにアクセスできるIFを備えたパッケージを用意しました。
xshoji/go-rough-yaml: go-rough-yaml provide accessor methods to edit yaml.
https://github.com/xshoji/go-rough-yaml
特徴
go-rough-yamlの特徴は以下の3つです。
- シンプルなインターフェース
- スキーマレス
- map構造の順序の保持
以下に使い方と合わせて特徴を詳しく説明します。
使い方
1行(yamlは複数行ですが...)でyamlを読み込めます。
roughYaml := goroughyaml.FromYaml(`
ddd:
ccc:
c: value-c
bbb:
- 10
aaa:
zzz: value-zzz
`)
入れ子構造の値の有無を意識せず、メソッドチェーンで目的の値を参照、更新できます。
// get value
roughYaml.
Get("ddd").
Get("ccc").
Get("c").Value() // => value-c
roughYaml.
Get("xxx").
Get("xxx").Value() // => nil
// set value
roughYaml.
Get("aaa").
Set("zzz", nil)
roughYaml.
Get("aaa").
Get("zzz").Value()) // -> nil
// delete key
roughYaml.Delete("aaa")
roughYaml.Get("aaa").Value()) // -> nil
このように、メソッドチェーンでゆるくyamlを扱えます。つまり、構造体の定義が不要(=スキーマレス)です。
yamlへ戻す場合も1行で済みます。かつ、map構造の順序が保持されます。
// print as yaml
/**
ddd:
ccc:
c: value-c
bbb:
- 10
*/
roughYaml.ToYaml()
まとめ
go-rough-yamlの実装にあたって、以下の3点についての理解が浅かったため、苦労しました。
- ポインタ
- interface{}
- nil
golangであるあるなトピックなので今更、という感じですが、今回実際に苦しめられてかなり学びがありました。
特にinterface{}
とnil
の話は、過去に他の方がよくまとめてくれていたのでそれらの情報にとても助けられました。先人の知識はとても大切です。
基本的な操作しか実装、テストしていないので、バグや必要な機能がもしあればIssueでご連絡いただければ対応します。
golangでyamlをゆるく取り扱い方に少しでも貢献できていたら嬉しいです。もしかしたらjson版も作るかもです。