Go で struct を embed して、inline する

構造体の形をシンプルにし、yaml での Marshaling にも対応させる


Posted on Mon, Feb 24, 2020
Tags golang, yaml

Go で struct は embed (埋め込み) できる

type ReadWriter struct {
    reader *Reader
    writer *Writer
}

go では継承がないため、embed という形で field を引き継がせることができる。 interface の場合は interface のみを embed でき、struct の場合は interface, struct を embed できる。

参考:

さて、go で yaml を扱う際によく利用される gopkg.in の yaml.v2 には struct field tag にて flag を与えることができる。 yaml - GoDoc

この中で inline という flag があり、これを embed した struct と組み合わせると下記のように yaml 出力時に階層を一つ下げて export, import ができる。

package main

import (
    "fmt"
    "gopkg.in/yaml.v2"
)

type A struct {
    Foo string `yaml:"foo"`
    Bar int `yaml:"bar"`
}

type B struct {
    EmA A
}

type C struct {
    EmA A `yaml:"a,inline"`
}

func main(){
    a := A{
        Foo: "foo",
        Bar: 1,
    }
    b := B{
        EmA: a,
    }
    c := C{
        EmA: a,
    }

    aBytes, _ := yaml.Marshal(a)
    fmt.Printf("%s\n", aBytes)

    bBytes, _ := yaml.Marshal(b)
    fmt.Printf("%s\n", bBytes)

    cBytes, _ := yaml.Marshal(c)
    fmt.Printf("%s\n", cBytes)
}

この実行結果は下記のようになる。

foo: foo
bar: 1

ema:
  foo: foo
  bar: 1

foo: foo
bar: 1

この embed して yaml では inline 化するメリットは、例えば Config 構造体のような、いくつかの config を束ねて、ファイルへ marshal したいというようなケースに不要な階層を一つ削ることができる点がある。

configA:
    foo: foo
    bar: 1
configB:
    baz: baz

// これを下記のように marshal できる
foo: foo
bar: 1
baz: baz

もちろん、これだと例えば baz field がどの config 構造体に紐づくか分かりづらいという観点もある。 そんなときは、struct field tag に、yaml:"git_api_url" というようなユニーク性を持つ名前を与えることで混乱は避けられる。