リストまたは値のいずれかを取るYAMLの読み込み

# 概要
通常はリストを取るが，要素が一つしかない場合はリストにせず値を直接書いて良い，
というフォーマットの YAML 文書を読み込む方法．

**コメントにてMarshaler/Unmarshalerについて教えていただいたので修正 (2017年2月1日)**

# 例
次の文書のように author はリストを受け取るのだが，

```book.yml
title: some book
author:
  - Alice
  - Bob
```

著者が一人しかいない場合は，

```book.yml
title: some book
author:
  - Alice
```

と

```book.yml
title: some book
author:　Alice
```

のどちらでも OK というような場合を考える．
この時，次のような構造体を用意して，

```go
type Book struct {
    Title string
    Author []string
}
```

[yaml.Unmarshal](https://github.com/go-yaml/yaml) で次のように読み込もうとすると，`author` がリストになっていない場合にエラーになる．

```go
t := &Book{}

// data は bool.yml のバイト列
err := yaml.Unmarshal([]byte(data), t)
if err != nil {
  log.Fatalf("error: %v", err)
}
```

# 解決策
Author の型が string と []string の二種類の場合があることが問題なので，
まずは一般的な interface{} で受けておいて，後で変換することにする．

補助の構造体を使って読み込むには，[Unmarshaler](https://godoc.org/gopkg.in/yaml.v2#Unmarshaler)を実装すれば良いらしい．（参考：[Decoding YAML in Go](https://mlafeldt.github.io/blog/decoding-yaml-in-go/)）

まず， Unmarshaler を適用させたい項目用の型を定義する．

```go
type ListOrString []string

type Book struct {
    Title string
    Author ListOrString
}
```

次に，この ListOrString 型に Unmarshaler を実装させる．

```go
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)に値を保存する．





# 解決策（旧）
**最初に投稿したバージョンも一応残しておきます**．

Author の型が string と []string の二種類の場合があることが問題なので，
まずは一般的な interface{} で受けておいて，後で変換することにする．

```go
type Book struct {
    Title string
    RawAuthor interface{} `yaml:"author"`
    Author []string `yaml:"_author,omitempty"`
}
```

YAML 文書中の author 属性を一旦 interface{} 型の RawAuthor に入れるためにタグを設定する．
タグを設定していても Author 属性があると，そちらが優先されてしまうようなので，
Author 属性の方には，ダミーの _author を与えておく．

次に， refrect を使って RawAuthor から Author へ値をコピーする．
次のメソッドはより一般的に， 属性名を与えると Raw 付きの属性値から与えられた属性に値をコピーする．

```go
func (t *Book) parseRawField(name string) (err error) {

    r := reflect.Indirect(reflect.ValueOf(t))
    src := r.FieldByName(fmt.Sprintf("Raw%s", name))
    dest := r.FieldByName(name)

    switch raw := src.Interface().(type) {
    // 単一文字列の場合
    case string:
        dest.Set(reflect.ValueOf([]string{raw}))

    // リストの場合
    case []interface{}:
        list := make([]string, len(raw))
        for i, r := range raw {
            v, ok := r.(string)
            if !ok {
                return fmt.Errorf("Cannot convert to a string: %v", r)
            }
            list[i] = v
        }
        dest.Set(reflect.ValueOf(list))
    }
    return

}
```

これを用いて，

```go
t := &Book{}

// data は bool.yml のバイト列
err := yaml.Unmarshal([]byte(data), t)
if err != nil {
  log.Fatalf("error: %v", err)
}
t.parseRawField("Author")
```

なお，この後 Marshal することがなければ， RawAuthor は nil 設定しても良い．
逆に Marshal する場合は， Author の方を nil にしてからでないと _author が出力されてしまう．