golang のyaml library の使い方

Go 言語にはyaml を入出力するためのライブラリhttp://godoc.org/gopkg.in/yaml.v2:package yaml があり、これを使うとGoの構造体とyamlとの変換が、わりに簡単にできる。

ところが、構造を持たないようなデータを読もうと思うとなんだかうまくいかない。例えばディレクトリ構造を表現した次のYAMLを読むにはどうしたらいいのかわからない。

- index.html
- index2.html
- ja:
  - test1.html
  - test2.html

なんか色々調べていると、yaml.v1の時にはかなりプリミティブなコールバックがあって、それで手間はかかるけど色々出来たっぽい。v2 でライブラリが直接構造体に書き込むようになったので話が面倒になっている印象

Unmarshaler

v2 にも手動でunmarshal する機構があり、type Unmarshaler で表現されている。これは UnmarshalYAMLという関数を定義してあるタイプで、読み込みたい構造体にこの関数を定義しておくと、読み込み時にコールバックしてくれるらしい。

まず、NamedSubTree という型を定義する。これがディレクトリのファイル、もしくはディレクトリを表現する。ディレクトリの時にはSubがnilになる。

type NamedSubtree struct {
	Str string
	Sub [] NamedSubtree
}

でこの型に対してUnmarshalYAMLを定義する。

func (this * NamedSubtree) 
                 UnmarshalYAML(unmarshal func(interface{}) error) error {
	var s string
	e := unmarshal(&s)    // ファイルを仮定して読み出してみる。
	if e == nil {
		this.Str = s
		return nil          // 成功したら終了
	} 
                                  // 失敗したらディレクリだと思ってMapで読む。
	var m map[string]([] NamedSubtree)
	e2 := unmarshal(&m)
	if e2 == nil {
		for k := range m {
			this.Str = k
			this.Sub = m[k]
		}
	} else {
		pp.Print(e2)
                return e2
	}
	return nil
}

すると、こんな感じにして読みだすことができる。

	dat, _ := ioutil.ReadFile("tree.yaml")
	var t [] NamedSubtree
	e := yaml.Unmarshal(dat, &t)

Marshaler

同様にMarshal側もいじることができる。同じようにMarshalYAMLという関数を定義する。

func (this NamedSubtree)	MarshalYAML() (interface{},  error) {
	if this.Sub == nil {   // Subが空ならファイルなので文字列として出力
		return this.Str, nil
	} else {                   // Subが空でないならmapとして出力
		m := make(map[string]([] NamedSubtree))
		m[this.Str] = this.Sub
		return m, nil
	}
}

呼び出し側はこんな感じ。

	out, _ := yaml.Marshal(t)
	fmt.Println(string(out))

所感

本当はLeafとNodeを別の型にしたかったのだけど、どうにもうまくできなかった。古いAPIならできそうなのになあ。。