🙄

[備忘録]Goで構造体のタグを上書きしてUnmarshalする

2024/09/29に公開

※前提として、gopkg.in/yaml.v3Unmarshal()している。

下記の例ではFlowスタイルのYAMLを構造体のfooUnmarshal()しようとしているが、fooにタグが無いために失敗している。

main.go
package main

import (
	"fmt"

	"gopkg.in/yaml.v3"
)

type foo struct {
	Value string
}

func main() {
	flattenYaml := `{Value: something}`

	f := foo{}
	if err := yaml.Unmarshal([]byte(flattenYaml), &f); err != nil {
		panic(err)
	}
	fmt.Printf("f: %+v\n", f)
}
実行結果
> go run main.go
f: {Value:}
>

タグの無い構造体にエイリアスを定義したうえで、gopkg.in/yaml.v3で定義しているインターフェースのobsoleteUnmarshalerを満たすようメソッドを実装する。
すると、Unmarshal()実行時にそのメソッドが実行される。
https://github.com/go-yaml/yaml/blob/f6f7691b1fdeb513f56608cd2c32c51f8194bf51/yaml.go#L40-L42

main.go
package main

import (
	"fmt"

	"gopkg.in/yaml.v3"
)

type foo struct {
	Value string
}

// fooのエイリアスを定義
type bar foo

// obsoleteUnmarshaler を満たすメソッドを定義
func (b *bar) UnmarshalYAML(unmarshal func(interface{}) error) error {
	// 任意のタグを付与した無名構造体を定義する。
	s := &struct {
		Value string `yaml:"Value,flow"`
	}{}

	// barの代わりに上記で定義した無名構造体をUnmarshalに渡す。
	if err := unmarshal(s); err != nil {
		return err
	}

	// 成功していれば無名構造体に結果が入っているので、barに渡す。
	b.Value = s.Value

	return nil
}

func main() {
	flattenYaml := `{Value: something}`

	f := foo{}
	if err := yaml.Unmarshal([]byte(flattenYaml), &f); err != nil {
		panic(err)
	}
	fmt.Printf("f: %+v\n", f)

	b := bar{}
	if err := yaml.Unmarshal([]byte(flattenYaml), &b); err != nil {
		panic(err)
	}
	fmt.Printf("b: %+v\n", b)
}
実行結果
> go run main.go
f: {Value:}
b: {Value:something}
>

Discussion