リストまたは値のいずれかを取るYAMLの読み込み
Posted: , Modified: Go YAML Qiita
本稿は Qiita 投稿記事 のバックアップです.
概要
通常はリストを取るが,要素が一つしかない場合はリストにせず値を直接書いて良い, というフォーマットの YAML 文書を読み込む方法.
例
次の文書のように author はリストを受け取るのだが,
title: some book
author:
- Alice
- Bob
著者が一人しかいない場合は,
title: some book
author:
- Alice
と
title: some book
author: Alice
のどちらでも OK というような場合を考える. この時,次のような構造体を用意して,
type Book struct {
Title string
Author []string
}
yaml.Unmarshal で次のように読み込もうとすると,author
がリストになっていない場合にエラーになる.
t := &Book{}
// data は bool.yml のバイト列
err := yaml.Unmarshal([]byte(data), t)
if err != nil {
log.Fatalf("error: %v", err)
}
解決策
Author の型が string と []string の二種類の場合があることが問題なので, まずは一般的な interface{} で受けておいて,後で変換することにする.
補助の構造体を使って読み込むには,Unmarshalerを実装すれば良いらしい.(参考:Decoding YAML in Go)
まず, Unmarshaler を適用させたい項目用の型を定義する.
type ListOrString []string
type Book struct {
Title string
Author ListOrString
}
次に,この ListOrString 型に Unmarshaler を実装させる.
func (e *ListOrString) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
var aux interface{}
if err = unmarshal(&aux); err != nil {
return
}
switch raw := aux.(type) {
case string:
*e = []string{raw}
case []interface{}:
list := make([]string, len(raw))
for i, r := range raw {
v, ok := r.(string)
if !ok {
return fmt.Errorf("An item in evn cannot be converted to a string: %v", aux)
}
list[i] = v
}
*e = list
}
return
}
UnmarshalYAML の中では,unmarshal関数を使って中間的な構造体(aux)に値を読み込ませる. その後,必要な変換を行なって,目的の変数(*e)に値を保存する.