リフレクション

リフレクション #

型情報を値として取得する #

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
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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")
	}
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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
	}
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: reflect

構造体のフィールドのタグ情報を取得する #

構造体のフィールドのタグには key:"value" という書式で任意のデータをもたせることができる。

key:"value" のセットは空白区切りで複数指定できる。

このタグ情報はフィールド情報である reflect.StructField から Tag メソッドで取得できる reflect.StructTag 型の値である。

reflect.StructTagGet メソッドで 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"`
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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...)
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: 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")
	}
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: reflect