golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) YAMLの使い方

golang.png


golangにはYAMLの標準パーッケージはありません。
なので yaml.v2 を使って、
YAML から go言語のデータ型(構造体やマップなど) に変換(Unmarshal,Decode) と、
その逆で go言語のデータ型から YAML に変換(Marshal,Encode) します。

以下、パッケージのダウンロード、Unmarshal,Decode,Marshal,Encode それぞれの使い方、
構造体のタグ(アノテーション) についてサンプルコードで説明しています。

※データを読み込むだけなら yaml,json 形式どちらも扱えます。


YAML 公式ページ

ライブラリ yaml.v2



目次



ダウンロード - download

$ go get gopkg.in/yaml.v2

使い方


YAML から map にする - Unmarshal

package main

import (
    "fmt"
    "log"
    "os"

    yaml "gopkg.in/yaml.v2"
)

func main() {
    var m map[string]interface{}

    data := `
  name: Tanaka
  age: 30
`
    // JSONでも同じで、ポインタで渡す
    err := yaml.Unmarshal([]byte(data), &m)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%v\n", m) // map[name:Tanaka age:30]
}


YAML から構造体にする - UnmarshalStrict

厳格モード。キーの重複や、構造体にフィールド名が無い場合にエラーになる

package main

import (
    "fmt"
    "log"

    yaml "gopkg.in/yaml.v2"
)

type Profile struct {
    Name string `yaml:"name"`
    Age  int    `yaml:"age"`
}

func main() {
    data := `
  name: Tanaka
  age: 31
`
    var p Profile
    err := yaml.UnmarshalStrict([]byte(data), &p)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%#v\n", p) // main.Profile{Name:"Tanaka", Age:31}


    // 以下は厳格モードなのでエラー
    // name が重複でエラーになる
    // line 3: field name already set in type main.Profile
    // unknown は Profile のフィールド名にないのでエラー
    // line 4: field unknown not found in type main.Profile
    data = `
  name: Tanaka
  name: Suzuki
  unknown: 1000
`

    err = yaml.UnmarshalStrict([]byte(data), &p)
    if err != nil {
        log.Fatal(err)
    }
}


map から YAML([]byte) に変換 - Marshal

Go言語のデータ型(struct,map,slice)から YAML に変換する

package main

import (
    "fmt"
    "log"
    "os"

    yaml "gopkg.in/yaml.v2"
)

func main() {
    m := map[string]interface{}{
        "name": "Tanaka",
        "age":  30,
    }

    data, err := yaml.Marshal(m)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(data))
    // age: 30
    // name: Tanaka
}



デコード - NewDecoder - Decode

YAML データから Go のデータ型(map,slice,struct) に変換する

package main

import (
    "fmt"
    "log"
    "os"
    "strings"

    yaml "gopkg.in/yaml.v2"
)

func main() {
    var m map[string]interface{}

    data := `
  name: Tanaka
  age: 30
`

    d := yaml.NewDecoder(strings.NewReader(data))

    // UnmarshalStrict と同じで厳格モードにするなら以下を指定する
    // デフォルトは、厳格モードは無効
    // 当然ですが、 d.Decode(&m) の前に指定する必要がある
    d.SetStrict(true)

    err := d.Decode(&m)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%v\n", m) // map[name:Tanaka age:30]
}


ファイルから読み込む

package main

import (
    "fmt"
    "log"
    "os"

    yaml "gopkg.in/yaml.v2"
)

func main() {
    f, err := os.Open("hello.yml")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    d := yaml.NewDecoder(f)

    var m map[string]interface{}

    if err := d.Decode(&m); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%v\n", m) // map[name:Tanaka age:30]
}



エンコード - NewEncoder - Encode

Goのデータ型(map,slice,struct) などから、YAML に変換して書き込む

package main

import (
    "bytes"
    "fmt"
    "log"

    yaml "gopkg.in/yaml.v2"
)

func main() {
    data := map[string]interface{}{"name": "Tanaka", "age": 31}

    var b bytes.Buffer
    d := yaml.NewEncoder(&b)

    err := d.Encode(&data)
    if err != nil {
        log.Fatal(err)
    }

    d.Close() // 残りのデータを書き込んでエンコーダを終了する

    fmt.Printf("%s\n", b.String())
    // name: Tanaka
    // age: 30
}


ファイルに書き込む

package main

import (
    "log"
    "os"

    yaml "gopkg.in/yaml.v2"
)

func main() {
    data := map[string]interface{}{"name": "Tanaka", "age": 31}

    f, err := os.OpenFile("hello.yml", os.O_WRONLY|os.O_CREATE, 0664)
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    d := yaml.NewEncoder(f)

    if err := d.Encode(&data); err != nil {
        log.Fatal(err)
    }

    d.Close()
}



構造体のタグ - tags

タグの指定方法

yaml:"[名前][,オプション1[,オプション2]]"


オプション

  • omitempty
    構造体の値が初期値だった場合は、YAMLとして出力されない
    初期値とは string型 なら 空文字、int型 なら 0 など


  • flow YAML のフロースタイルで出力する
    フロースタイルとは JSON のような形式(デフォルトはブロックスタイル) { name: Tanaka, age: 30 }


  • inline
    構造体またはマップに使うことができる


  • "-"
    タグの名前に yaml:"-" を指定した場合は、そのフィールドは無視される


名前を指定しても Name string `yaml:"name"` は Name でも name でもどちらでもよい。
構造体のフィールド名が小文字 name string `yaml:"name"` の場合は、"-" 同様で無視される。



inline,flow を指定した場合と指定しない場合

package main

import (
    "fmt"
    "log"

    yaml "gopkg.in/yaml.v2"
)

func main() {

    { // オプションの指定なし
        u := struct {
            Name  string         `yaml:"name"`
            Age   int            `yaml:"age"`
            Score map[string]int `yaml:"score"`
        }{
            Name:  "Tanaka",
            Age:   30,
            Score: map[string]int{"1st": 10, "2nd": 20, "3rd": 30},
        }

        data, err := yaml.Marshal(u)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(string(data))
        // name: Tanaka
        // age: 30
        // score:
        //   1st: 10
        //   2nd: 20
        //   3rd: 30

    }

    { // inline を指定

        u := struct {
            Name  string
            Age   int
            Score map[string]int `yaml:"score,inline"`
        }{
            Name:  "Tanaka",
            Age:   30,
            Score: map[string]int{"1st": 10, "2nd": 20, "3rd": 30},
        }

        data, err := yaml.Marshal(u)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(string(data))
        // name: Tanaka
        // age: 30
        // 1st: 10
        // 2nd: 20
        // 3rd: 30
    }

    { // flow を指定

        u := struct {
            Name  string
            Age   int
            Score map[string]int `yaml:"score,flow"`
        }{
            Name:  "Tanaka",
            Age:   30,
            Score: map[string]int{"1st": 10, "2nd": 20, "3rd": 30},
        }

        data, err := yaml.Marshal(u)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(string(data))
        // name: Tanaka
        // age: 30
        // score: {1st: 10, 2nd: 20, 3rd: 30}
    }
}