リフレクション #
型情報を値として取得する #
reflect
パッケージを使うと任意の値の型情報を reflect.Type
型の値として取得できる。
reflect.Type
は interface であり、型名や型の種別、割当サイズなどの型情報を返すメソッドが実装される。
reflect.Type
の実装は型の種別ごとに異なり、例えば型名を返すメソッドや型の種別を返すメソッドはどの型でも共通して実装されているが、
構造体のフィールド情報を取得するメソッドは構造体型の reflect.Type
でしか実装されておらず、それ以外の型の reflect.Type
でフィールド情報取得メソッドを実行すると panic が起きる。
reflect.Type
を取得するには、任意の値に reflect.TypeOf
関数を実行する。
package main
import (
"errors"
"fmt"
"reflect"
)
func main() {
// 型情報の取得
rt := reflect.TypeOf(1.)
// 型名
fmt.Println(rt.Name()) // == float64
// 型の種別
fmt.Println(rt.Kind()) // == float64
// 型の割当サイズ
fmt.Println(rt.Size()) // == 8
fmt.Println("-- スライス型独自の型情報")
rt = reflect.TypeOf([]int{})
// 要素の型情報
fmt.Printf("%#v\n", rt.Elem()) // == &reflect.rtype{size:0x8, ptrdata:0x0, hash:0xf75371fa, tflag:0xf, align:0x8, fieldAlign:0x8, kind:0x2, equal:(func(unsafe.Pointer, unsafe.Pointer) bool)(0x10032e0), gcdata:(*uint8)(0x10e85c0), str:951, ptrToThis:27104}
fmt.Println("-- 配列型独自の型情報")
rt = reflect.TypeOf([1]int{0})
// 要素の型情報
fmt.Printf("%#v\n", rt.Elem()) // == &reflect.rtype{size:0x8, ptrdata:0x0, hash:0xf75371fa, tflag:0xf, align:0x8, fieldAlign:0x8, kind:0x2, equal:(func(unsafe.Pointer, unsafe.Pointer) bool)(0x10032e0), gcdata:(*uint8)(0x10e85c0), str:951, ptrToThis:27104}
// 配列の長さ
fmt.Println(rt.Len()) // == 1
fmt.Println("-- マップ型独自の型情報")
rt = reflect.TypeOf(map[int]string{})
// 値の要素の型情報
fmt.Printf("%#v\n", rt.Elem()) // == &reflect.rtype{size:0x10, ptrdata:0x8, hash:0xe0ff5cb4, tflag:0x7, align:0x8, fieldAlign:0x8, kind:0x18, equal:(func(unsafe.Pointer, unsafe.Pointer) bool)(0x1003440), gcdata:(*uint8)(0x10d1618), str:4914, ptrToThis:35040}
// キーの要素の型情報
fmt.Printf("%#v\n", rt.Key()) // == &reflect.rtype{size:0x8, ptrdata:0x0, hash:0xf75371fa, tflag:0xf, align:0x8, fieldAlign:0x8, kind:0x2, equal:(func(unsafe.Pointer, unsafe.Pointer) bool)(0x10032e0), gcdata:(*uint8)(0x10e8d20), str:951, ptrToThis:27168}
fmt.Println("-- 構造体型独自の型情報")
rt = reflect.TypeOf(User{})
// フィールド数
fmt.Println(rt.NumField()) // == 2
// フィールドの型情報
fmt.Printf("%#v\n", rt.Field(0)) // == reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x10d6b60), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
// メソッド数
fmt.Println(rt.NumMethod()) // == 1
// メソッドの型情報
fmt.Printf("%#v\n", rt.Method(0)) // == reflect.Method{Name:"String", PkgPath:"", Type:(*reflect.rtype)(0xc00004e180), Func:reflect.Value{typ:(*reflect.rtype)(0xc00004e180), ptr:(unsafe.Pointer)(0xc00000e048), flag:0x13}, Index:0}
fmt.Println("-- 関数型独自の型情報")
rt = reflect.TypeOf(Hello)
// 引数の数
fmt.Println(rt.NumIn()) // == 1
// 引数の型情報
fmt.Printf("%#v\n", rt.In(0)) // == &reflect.rtype{size:0x10, ptrdata:0x8, hash:0xe0ff5cb4, tflag:0x7, align:0x8, fieldAlign:0x8, kind:0x18, equal:(func(unsafe.Pointer, unsafe.Pointer) bool)(0x1003440), gcdata:(*uint8)(0x10fc9e0), str:5178, ptrToThis:39616}
// 返り値の数
fmt.Println(rt.NumOut()) // == 2
// 返り値の型情報
fmt.Printf("%#v\n", rt.Out(0)) // == &reflect.rtype{size:0x10, ptrdata:0x8, hash:0xe0ff5cb4, tflag:0x7, align:0x8, fieldAlign:0x8, kind:0x18, equal:(func(unsafe.Pointer, unsafe.Pointer) bool)(0x1003440), gcdata:(*uint8)(0x10fc9e0), str:5178, ptrToThis:39616}
}
type User struct {
Name string
Age int
}
func (u User) String() string {
return fmt.Sprintf("%s(%d)", u.Name, u.Age)
}
func Hello(name string) (string, error) {
if name == "invalid" {
return "", errors.New("invalid name")
}
return fmt.Sprintf("Hello %s", name), nil
}
参考ドキュメント: reflect
型情報を比較する #
reflect.Type
がどの型の型情報であるかを判別するには Type.Kind()
メソッドで reflect.Kind
型の値を取得して定数と比較する。
package main
import (
"fmt"
"reflect"
)
func main() {
// 型情報の取得
rt := reflect.TypeOf(1.)
// 型の判別
switch rt.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Println("this is int")
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fmt.Println("this is uint")
case reflect.Float32, reflect.Float64:
fmt.Println("this is float")
case reflect.String:
fmt.Println("this is string")
case reflect.Slice:
fmt.Println("this is slice")
case reflect.Array:
fmt.Println("this is array")
case reflect.Map:
fmt.Println("this is map")
case reflect.Struct:
fmt.Println("this is struct")
case reflect.Bool:
fmt.Println("this is bool")
}
}
参考ドキュメント: reflect
任意の型の値を引数とする関数を定義する #
reflect.ValueOf
関数を使うと任意の型の値を reflect.Value
型の値へ変換できる。
これを使って任意の型の値を引数とする関数を定義する。
reflect.Value
を受け取った関数は、受け取った値の型情報を使って適切に処理を行う。
この例では任意の数値を float64 に変換する関数を定義する。
package main
import (
"fmt"
"reflect"
)
func main() {
i := int64(1)
u := uint64(2)
f := float64(3)
rv := reflect.ValueOf(i)
fmt.Println(NumberToFloat(rv)) // == 1
rv = reflect.ValueOf(u)
fmt.Println(NumberToFloat(rv)) // == 2
rv = reflect.ValueOf(f)
fmt.Println(NumberToFloat(rv)) // == 3
}
func NumberToFloat(rv reflect.Value) float64 {
rt := rv.Type()
switch rt.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(rv.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(rv.Uint())
case reflect.Float32, reflect.Float64:
return rv.Float()
default:
return 0
}
}
参考ドキュメント: reflect
リフレクションで変数を更新する #
reflect.Value
に変換した変数の値を更新したい場合、reflect.Value
の初期化時に変数のポインタを渡す。
具体的には、変数 v を対象とする場合 reflect.ValueOf(&v)
とする。
こうすることで reflect.Value
は元の変数のアドレスを保持した状態になり、Value.Set
メソッドなどで変数の値を更新できる。
package main
import (
"fmt"
"reflect"
)
func main() {
var i int = 100
// 変数 i を reflect.Value 型の値に変換
// ValueOf にポインタを渡して Elem でデリファレンスすることで「元の変数のアドレスを持った整数型の reflect.Value」ができる
rv := reflect.ValueOf(&i).Elem()
// 変数を変更できるかどうか判定
fmt.Println(rv.CanSet()) // == true
// 値の変更
// 整数専用の SetInt を使う
rv.SetInt(200)
// 変更の確認
fmt.Println(i) // == 200
// 値の変更
// 汎用の Set を使う
rv.Set(reflect.ValueOf(300))
// 変更の確認
fmt.Println(i) // == 300
}
参考ドキュメント: reflect
構造体のフィールド一覧を取得する #
package main
import (
"fmt"
"reflect"
)
func main() {
rt := reflect.TypeOf(User{})
// インデックスでフィールド一覧
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
// フィールド名
fmt.Println("---", f.Name)
// フィールドの型
fmt.Println(f.Type)
// タグ
fmt.Println(f.Tag)
}
}
type User struct {
Name string `json:"name"`
Age int `json:"age" toml:"age"`
height float64
weight float64
}
参考ドキュメント: reflect
構造体のフィールドのタグ情報を取得する #
構造体のフィールドのタグには key:"value"
という書式で任意のデータをもたせることができる。
key:"value"
のセットは空白区切りで複数指定できる。
このタグ情報はフィールド情報である reflect.StructField
から Tag
メソッドで取得できる reflect.StructTag
型の値である。
reflect.StructTag
は Get
メソッドで key 名でのタグの検索ができる。
package main
import (
"fmt"
"reflect"
)
func main() {
rt := reflect.TypeOf(S1{})
// インデックスでフィールド一覧
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
// フィールド名
fmt.Println("---", f.Name)
if t := f.Tag.Get("t1"); t != "" {
fmt.Println("t1:", t)
}
if t := f.Tag.Get("t2"); t != "" {
fmt.Println("t2:", t)
}
}
}
type S1 struct {
F1 string `t1:"aaa"`
F2 int `t1:"bbb" t2:"xxx"`
F3 bool `t2:"yyy"`
}
参考ドキュメント: reflect
ゼロ値であるかを調べる #
reflect.Value
には値がゼロ値であるかどうかを判定する IsZero
メソッドがある。
package main
import (
"fmt"
"reflect"
)
func main() {
var v string
fmt.Println(reflect.ValueOf(v).IsZero()) // == true
v = ""
fmt.Println(reflect.ValueOf(v).IsZero()) // == true
v = "a"
fmt.Println(reflect.ValueOf(v).IsZero()) // == false
}
参考ドキュメント: reflect
ゼロ値を取得する #
reflect.Zero
関数を使うと任意の型のゼロ値を取得できる。
package main
import (
"fmt"
"reflect"
"time"
)
func main() {
rv := reflect.Zero(reflect.TypeOf(1))
fmt.Println(rv) // == 0
type S1 struct {
F1 string
F2 bool
}
rv = reflect.Zero(reflect.TypeOf(S1{}))
fmt.Println(rv) // == {"" false}
rv = reflect.Zero(reflect.TypeOf(time.Now()))
fmt.Println(rv) // == 0001-01-01 00:00:00 +0000 UTC
}
参考ドキュメント: reflect
リフレクションで数値を扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
p("// 型の取得")
p(reflect.TypeOf(0)) //=> int
p("// 型の種別の比較")
// 比較対象の定義は https://golang.org/pkg/reflect/#Kind
p(reflect.TypeOf(0).Kind() == reflect.Int) //=> true
p("// 型の比較")
p(reflect.TypeOf(0) == reflect.TypeOf(100)) //=> true
p("// 変数へ reflect 経由で値をセットする")
var i int
// reflect.Value 型に変換
// ポインタでないと値の変更ができないので &i で変数を渡して .Elem() でポインタの値を返している
v := reflect.ValueOf(&i).Elem()
// 値をセットできる
p(v.CanSet()) //=> true
// 整数値のセット
v.SetInt(100)
p(i) //=> 100
// Set で汎用的に reflect.Value をセットできる | Type が異なる場合は panic になる
v.Set(reflect.ValueOf(200))
p(i) //=> 200
p("// reflect から Interface に変換し int に変換する")
if i2, ok := v.Interface().(int); ok {
p(i2) //=> 200
}
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションで文字列を扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
p("// 型の取得")
p(reflect.TypeOf("")) //=> string
p("// 型の種別の比較")
p(reflect.TypeOf("").Kind() == reflect.String) //=> true
p("// 型の比較")
p(reflect.TypeOf("") == reflect.TypeOf("hello")) //=> true
p("// 変数へ reflect 経由で値をセットする")
var s string
// reflect.Value 型に変換
// ポインタでないと値の変更ができないので &i で変数を渡して .Elem() でポインタの値を返している
v := reflect.ValueOf(&s).Elem()
// 値をセットできる
p(v.CanSet()) //=> true
// 文字列のセット
v.SetString("hello world")
p(s) //=> hello world
// Set で汎用的に reflect.Value をセットできる | Type が異なる場合は panic になる
v.Set(reflect.ValueOf("hello new world"))
p(s) //=> hello new world
p("// reflect から Interface に変換し string に変換する")
if s2, ok := v.Interface().(string); ok {
p(s2) //=> hello new world
}
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションで配列を扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
p("// 型を取得する")
p(reflect.TypeOf([1]int{0})) //=> [1]int
p("// 配列の要素の型を取得する")
p(reflect.TypeOf([1]int{0}).Elem()) //=> int
p("// 型の比較")
p(reflect.TypeOf([1]int{0}).Kind() == reflect.Array) //=> true
p(reflect.TypeOf([1]int{0}) == reflect.TypeOf([1]int{100})) //=> true
p("// 配列型の作成")
rt := reflect.ArrayOf(1, reflect.TypeOf(0)) // 引数は要素数と要素の型
p("Type:", rt, "Kind:", rt.Kind()) //=> Type: [1]int Kind: array
p("// 要素数の取得")
p(rt.Len()) //=> 1
p("// 配列の作成と値のセット")
// New で与えた型の値が作成されポインタが返却されるので Elem で値を取得
// New で *[1]int 型の値が作成され Elem で [1]int 型に変換
rt = reflect.TypeOf([1]int{})
rv := reflect.New(rt).Elem()
// Index で任意の位置の要素 (reflect.Value) にアクセスできる
p(rv.Index(0))
// SetInt で整数値のセット
rv.Index(0).SetInt(100)
p(rv) //=> [100]
p("// Interface 経由で reflect.Value から [1]int へ変換")
p(rv.Interface().([1]int)) //=> [100]
p("// 定義済みの変数へ値をセット")
ary := [1]int{0}
reflect.ValueOf(&ary).Elem().Index(0).SetInt(500)
p(ary) //=> [500]
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションでスライスを扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
p("// 型の取得")
p(reflect.TypeOf([]int{})) //=> []int
p(reflect.TypeOf(([]int)(nil))) //=> []int
p("// 要素の型の取得")
p(reflect.TypeOf([1]int{0}).Elem()) //=> int
p("// 型の比較")
p(reflect.TypeOf([]int{}).Kind() == reflect.Slice) //=> true
p(reflect.TypeOf([]int{}) == reflect.TypeOf([]int{1, 2, 3})) //=> true
p("// スライス型の作成")
rt := reflect.SliceOf(reflect.TypeOf(0)) // 引数は要素数と要素の型
p("Type:", rt, "Kind:", rt.Kind()) //=> Type: []int Kind: slice
p("// スライスの作成")
// MakeSlice で要素の型、要素数、容量を指定
rv := reflect.MakeSlice(reflect.TypeOf([]int{}), 1, 1)
// Index で任意の位置の要素 (reflect.Value) にアクセスできる
p(rv.Index(0))
// SetInt で整数値のセット
rv.Index(0).SetInt(100)
p(rv) //=> [100]
// Append での追加もできる
rv = reflect.Append(rv, reflect.ValueOf(200))
p(rv) //=> [100 200]
// AppendSlice でスライス同士の結合もできる
rv = reflect.AppendSlice(rv, reflect.ValueOf([]int{300}))
p(rv) //=> [100 200 300]
p("// Interface 経由で reflect.Value から []int へ変換")
p(rv.Interface().([]int)) //=> [100 200 300]
p("// 定義済みの変数へ値をセット")
s := make([]int, 2, 2)
rv = reflect.ValueOf(&s).Elem()
rv.Index(0).SetInt(500)
rv.Index(1).SetInt(1000)
p(s) //=> [500]
p("// 定義済みの変数へ要素を追加")
s = []int{}
rv = reflect.ValueOf(&s).Elem()
rv.Set(reflect.Append(rv, reflect.ValueOf(2000))) // Append は新しい reflect.Value を作成するので Set で元の変数を上書きする
p(s) //=> [2000]
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションでマップを扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
p("// 型の取得")
p(reflect.TypeOf(map[string]int{})) //=> map[string]int
p(reflect.TypeOf((map[string]int)(nil))) //=> map[string]int
p("// キーの型の取得")
p(reflect.TypeOf(map[string]int{}).Key()) //=> string
p("// 値の型の取得")
p(reflect.TypeOf(map[string]int{}).Elem()) //=> int
p("// 型の比較")
p(reflect.TypeOf(map[string]int{}).Kind() == reflect.Map) //=> true
p(reflect.TypeOf(map[string]int{}) == reflect.TypeOf(map[string]int{"key1": 1})) //=> true
p("// マップ型の作成")
rt := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0)) // 引数はキーの型と値の型
p("Type:", rt, "Kind:", rt.Kind()) //=> Type: map[string]int Kind: map
p("// マップの作成")
rv := reflect.MakeMap(reflect.TypeOf(map[string]int{}))
// 値のセット
rv.SetMapIndex(reflect.ValueOf("key1"), reflect.ValueOf(100))
// キーの一覧取得
for _, k := range rv.MapKeys() {
p(k)
}
// 値の取得
p(rv.MapIndex(reflect.ValueOf("key1"))) //=> 100
p("// 定義済みの変数へ値をセット")
m := map[string]int{}
rv = reflect.ValueOf(&m).Elem()
rv.SetMapIndex(reflect.ValueOf("key2"), reflect.ValueOf(200))
rv.SetMapIndex(reflect.ValueOf("key3"), reflect.ValueOf(300))
p(m) //=> map[key2:200 key3:300]
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションで構造体を扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
type User struct {
Name string
Age int
}
p("// 型の取得")
p(reflect.TypeOf(User{})) //=> main.User
p("// 型の比較")
p(reflect.TypeOf(User{}).Kind() == reflect.Struct) //=> true
p(reflect.TypeOf(User{}) == reflect.TypeOf(User{Name: "user1", Age: 10})) //=> true
p("// 構造体型の作成")
t := reflect.StructOf([]reflect.StructField{
reflect.StructField{Name: "Name", Type: reflect.TypeOf("")},
reflect.StructField{Name: "Age", Type: reflect.TypeOf(0)},
})
p(t) //=> struct { Name string; Age int }
p("// 構造体の作成")
rv := reflect.New(reflect.TypeOf(User{})).Elem()
p("// フィールドの一覧")
rt := rv.Type()
p(rv, rt)
for i := 0; i < rt.NumField(); i++ {
// フィールドの取得
f := rt.Field(i)
// フィールド名
p(f.Name)
// 型
p(f.Type)
// タグ
p(f.Tag)
}
p("// フィールドの取得")
if f, ok := rt.FieldByName("Name"); ok {
p(f.Name, f.Type) //=> Name string
}
p("// フィールドの更新")
rv.Field(0).SetString("user1")
p(rv.Field(0)) //=> user1
p("// 定義済みの変数へ値をセット")
u := User{}
uv := reflect.ValueOf(&u).Elem()
uv.Field(0).SetString("user2")
uv.Field(1).SetInt(20)
p(u.Name, u.Age) //=> user2 20
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションで関数を扱う #
package main
import (
"fmt"
"reflect"
)
func main() {
fn := func(s string, i int) string {
out := ""
for j := 0; j < i; j++ {
out += s
}
return out
}
p("// 型の取得")
p(reflect.TypeOf(fn)) //=> func(string, int) string
p("// 型の比較")
p(reflect.TypeOf(fn).Kind() == reflect.Func) //=> true
p(reflect.TypeOf(fn) == reflect.TypeOf(func(s string, i int) string { return "" })) //=> true
p("// 引数の一覧")
fnt := reflect.TypeOf(fn)
for i := 0; i < fnt.NumIn(); i++ {
// 引数の型の取得
p(fnt.In(i))
}
p("// 返り値の一覧")
for i := 0; i < fnt.NumOut(); i++ {
// 返り値の型の取得
p(fnt.Out(i))
}
p("// 関数の実行")
fnv := reflect.ValueOf(fn)
out := fnv.Call([]reflect.Value{reflect.ValueOf("hello"), reflect.ValueOf(2)})
if s, ok := out[0].Interface().(string); ok {
p(s) //=> hellohello
}
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションでインターフェイスを扱う #
package main
import (
"fmt"
"reflect"
)
// インターフェイス宣言
type MyInterface1 interface {
GetZero() int
}
// MyInterface1 を実装する構造体の宣言
type MyStruct1 struct{}
func (s MyStruct1) GetZero() int {
return 0
}
// MyInterface2 を実装しない構造体の宣言
type MyStruct2 struct{}
func main() {
p("/* インターフェイス */")
// インターフェイスの初期化. int の 1 を interface{} にキャストする
i1 := interface{}(1)
// インターフェイスの値型の取得
rv := reflect.ValueOf(i1)
// インターフェイスであるかどうかの判定
p(rv.CanInterface()) //=> true
// インターフェイスの値型のタイプ型は元の int 型
p(rv.Type()) //=> int
// 任意のインターフェイス型の取得
// MyInterface1 のタイプ型を取得するために nil を *MyInterface1 にキャストしてタイプ型を取得してから Elem() でポインタを外す
// MyInterface1(nil) とすると MyInterface1 が nil を許容しないためにコンパイルエラーになる
it := reflect.TypeOf((*MyInterface1)(nil)).Elem()
// 任意のタイプ型がインターフェイスの実装であるかどうかの判定
// これだけなら Type Assertion でも同様のことができるが後述するスライスの要素型の判定などをしたい場合などに応用できる
s1 := MyStruct1{}
s2 := MyStruct2{}
p(reflect.TypeOf(s1).Implements(it)) //=> true
p(reflect.TypeOf(s2).Implements(it)) //=> false
// スライスの要素がインターフェイスの実装であるかどうかの判定
list := []MyStruct1{}
p(reflect.TypeOf(list).Elem().Implements(it)) //=> true
}
func p(a ...interface{}) {
fmt.Println(a...)
}
参考ドキュメント: reflect
リフレクションで構造体へ再帰的に関数を実行する #
構造体のフィールドのタグ情報にしたがって任意の処理を行う。
この時、フィールドが構造体・構造体のポインタ・構造体のスライスの場合は再帰的に処理をする。
例としてフィールドの値がゼロ値ならタグで指定したデフォルト値をセットする。
package main
import (
"fmt"
"reflect"
"strconv"
)
func main() {
// 単純な構造体の場合
type S1 struct {
F1 string `default:"hello"`
F2 int `default:"100"`
F3 float64 `default:"1.5"`
F4 bool `default:"true"`
}
s1 := &S1{}
structFieldRecursiveUpdater(s1, setDefaultValue)
fmt.Printf("%v\n", s1) //=> &{hello 100 1.5 true}
// フィールドに構造体・構造体のポインタ・構造体のスライスをもつ構造体の場合
type S2 struct {
F1 string `default:"hello"`
S1 S1
S1Ptr *S1
S1Slice []S1
}
s2 := &S2{
S1Ptr: &S1{},
S1Slice: []S1{{}, {}},
}
structFieldRecursiveUpdater(s2, setDefaultValue)
fmt.Printf("%v\n", s2) //=> &{hello {hello 100 1.5 true} 0xc4202a2840 [{hello 100 1.5 true} {hello 100 1.5 true}]}
fmt.Printf("%v\n", s2.S1Ptr) //=> &{hello 100 1.5 true}
// フィールドに自身の構造体ポインタを持つ構造体の場合
type S3 struct {
F1 string `default:"hello"`
S3 *S3
}
s3 := &S3{
S3: &S3{
S3: &S3{
S3: &S3{},
},
},
}
structFieldRecursiveUpdater(s3, setDefaultValue)
fmt.Printf("%v\n", s3) //=> &{hello 0xc4200e7f80}
fmt.Printf("%v\n", s3.S3) //=> &{hello 0xc4200e7fa0}
fmt.Printf("%v\n", s3.S3.S3) //=> &{hello 0xc4200e7fc0}
fmt.Printf("%v\n", s3.S3.S3.S3) //=> &{hello <nil>}
}
// 構造体のフィールドを再帰的に読み込んで関数 fn を実行する関数
func structFieldRecursiveUpdater(value interface{}, fn func(reflect.Value, reflect.StructTag)) {
v := reflect.Indirect(reflect.ValueOf(value))
t := v.Type()
switch t.Kind() {
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
ft := t.Field(i)
fv := v.FieldByName(ft.Name)
if ft.Type.Kind() == reflect.Struct {
// 構造体
structFieldRecursiveUpdater(fv.Addr().Interface(), fn)
} else if ft.Type.Kind() == reflect.Ptr && fv.IsNil() {
// nil ポインタなら処理しない
continue
} else if ft.Type.Kind() == reflect.Ptr && ft.Type.Elem().Kind() == reflect.Struct {
// 構造体のポインタ
structFieldRecursiveUpdater(fv.Interface(), fn)
} else if ft.Type.Kind() == reflect.Slice && ft.Type.Elem().Kind() == reflect.Struct {
// 構造体のスライス
structFieldRecursiveUpdater(fv.Interface(), fn)
} else {
// 任意の型
fn(fv, ft.Tag)
}
}
case reflect.Slice:
for i := 0; i < v.Len(); i++ {
e := v.Index(i)
structFieldRecursiveUpdater(e.Addr().Interface(), fn)
}
}
}
// 値がゼロ値ならタグで指定したデフォルト値をセットする関数
func setDefaultValue(v reflect.Value, tag reflect.StructTag) {
value := tag.Get("default")
if value == "" {
return
}
if v.Interface() != reflect.Zero(v.Type()).Interface() {
return
}
switch v.Kind() {
case reflect.String:
v.SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if i, err := strconv.ParseInt(value, 10, 64); err == nil {
v.SetInt(i)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if i, err := strconv.ParseUint(value, 10, 64); err == nil {
v.SetUint(i)
}
case reflect.Float32, reflect.Float64:
if f, err := strconv.ParseFloat(value, 64); err == nil {
v.SetFloat(f)
}
case reflect.Bool:
v.SetBool(value == "true")
}
}
参考ドキュメント: reflect