@int128

int128.hatenablog.com

Read dynamic type values from YAML in Golang

GolangYAMLを読み込む際、要素の型が実行時に決まる場合を考えます。例えば、以下のYAMLでは values で数値、文字列、マップの値が列挙されています。

values:
  - 100
  - foo
  - key: value

このようなYAMLを読み込む場合、型宣言を interface{} にすると実行時に型をチェックして値を処理できます。

type Spec struct {
    Values []interface{} `yaml:"values"`
}

func main() {
    spec := Spec{}
    yaml.NewDecoder(file).Decode(&spec)
    for _, value := range spec.Values {
        switch typedValue := value.(type) {
        case string:
            // 文字列を表示する
            fmt.Printf("string=%s\n", typedValue)
        case int:
            // 数値を表示する
            fmt.Printf("int=%d\n", typedValue)
        case map[interface{}]interface{}:
            // マップに含まれる key の値を表示する
            fmt.Printf("key=%+v\n", typedValue["key"])
        }
    }
}

この方法を応用するとYAMLの部分木を出力することも可能です。先ほどのYAMLに対して以下を実行すると key: value が表示されます。

   for _, value := range spec.Values {
        case map[interface{}]interface{}:
            // 部分木を表示する
            e := yaml.NewEncoder(os.Stdout)
            defer e.Close()
            e.Encode(typedValue)
    }

Full example

package main

import (
    "fmt"
    "os"

    "gopkg.in/yaml.v2"
)

type Spec struct {
    Values []interface{} `yaml:"values"`
}

func main() {
    file, err := os.Open("fixture.yaml")
    if err != nil {
        panic(err)
    }
    spec := Spec{}
    yaml.NewDecoder(file).Decode(&spec)

    for _, value := range spec.Values {
        switch typedValue := value.(type) {
        case string:
            fmt.Printf("string=%s\n", typedValue)
        case int:
            fmt.Printf("int=%d\n", typedValue)
        case map[interface{}]interface{}:
            e := yaml.NewEncoder(os.Stdout)
            defer e.Close()
            e.Encode(typedValue)
        }
    }
}

See also