Help us understand the problem. What is going on with this article?

Go 言語 reflect チートシート

はじめに

  • Go 言語で動的に型を扱いたい場合なんかに便利な reflect パッケージですが godoc に Example が少なく試しながら使い方を確認していたのでチートシートをまとめてみました。
  • The Go Playground でもそのまま実行できます。
package main

import (
    "fmt"
    "reflect"
)

func main() {
    {
        p("/* 数値 */")

        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 に変換")
        if i2, ok := v.Interface().(int); ok {
            p(i2) //=> 200
        }
    }

    {
        p("/* 配列 */")

        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 で値を取得
        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]
    }

    {
        p("/* スライス */")

        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("// 定義済みの変数へ値をセット (1)")
        s := make([]int, 2, 2)
        rv = reflect.ValueOf(&s).Elem()
        rv.Index(0).SetInt(500)
        rv.Index(1).SetInt(1000)
        p(s) //=> [500]

        p("// 定義済みの変数へ値をセット (2)")
        s = []int{}
        rv = reflect.ValueOf(&s).Elem()
        rv.Set(reflect.Append(rv, reflect.ValueOf(2000))) // Append は新しい reflect.Value を作成するので Set で元の変数を上書きする
        p(s)                                              //=> [2000]
    }

    {
        p("/* マップ */")

        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]
    }

    {
        p("/* 構造体 */")

        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
    }

    {
        p("/* 関数 */")

        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...)
}

インターフェイス編(追記)

package main

import (
    "reflect"
    "fmt"
)

// インターフェイス宣言
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...)
}

構造体を再帰的に処理する(2019-07-11 追記)

  • 構造体のフィールドのタグの記述に従って任意の処理を行う例
  • フィールドが構造体・構造体のポインタ・構造体のスライスの場合は再帰的に処理をする
  • 例としてフィールドの値がゼロ値ならデフォルト値をセットする
  • Playgroun
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")
    }
}

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした