reflect package に苦手意識がある人は多いのでは?と思います。
自分自身もあまり使ってなかったのですが、 encoding/json 的な package として excel に入出力するものを作った時にある程度理解が進みました。
ここでは、最低限のコードにより reflect package を少しだけ説明します。
reflect package とは
Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type.
A call to ValueOf returns a Value representing the run-time data. Zero takes a Type and returns a Value representing a zero value for that type.
https://pkg.go.dev/reflect@go1.17.5
reflect は実行時の reflection のための package です。
雑な理解としては interface{}
に対しての型情報や値の読み書きをすることができる package です。
詳細は godoc を参照してください。
サンプルコードと出力結果
この記事では、以下のコードを例とします。
package main
import (
"fmt"
"reflect"
)
type XXX struct {
Fullname string `json:"name"`
Number int `json:"number,omitempty"`
}
func main() {
x := XXX{Fullname: "aaa", Number: 123}
rt := reflect.TypeOf(x)
rv := reflect.ValueOf(x)
fmt.Printf("%s{\n", rt.Name())
for i := 0; i < rv.Type().NumField(); i++ {
k := rt.Field(i)
v := rv.Field(i)
switch v.Kind() {
case reflect.String:
fmt.Printf(" %s: string(%q), // tag %q\n", k.Name, v.String(), k.Tag.Get(`json`))
case reflect.Int:
fmt.Printf(" %s: int(%d), // tag %q\n", k.Name, v.Int(), k.Tag.Get(`json`))
default:
}
}
fmt.Printf("}\n")
}
出力結果は以下になります。
XXX{
Fullname: string("aaa"), // tag "name"
Number: int(123), // tag "number,omitempty"
}
構造体の各 Field の型情報 reflect.Type
上記コードの rt.Field(i)
の戻り値は、構造体の各 Field の情報です。
すなわち var x XXX
に対して x.Name
とか x.Number
とかで参照される Field 自体の情報です。
型の情報なので、ここで取り出した reflect.Type
には Tag 情報が含まれていて k.Tag.Get()
で取り出すことができます。
上記のコードの TypeOf に関する部分はおおよそ以下のようなものです。
x := XXX{Fullname: "aaa", Number: 123}
rt := reflect.TypeOf(x)
k := rt.Field(0) // Fullname string `json:"name"` に対応する部分
fmt.Printf("%q : tag %q\n", k.Name, k.Tag.Get(`json`))
// -> "Fullname" : tag "name" と表示される
構造体の各 Field の値情報 reflect.Value
上記コードの rv.Field(i)
の戻り値は、構造体の各 Field で保持される値の情報です。
reflect.Value
からは例えば x.Fullname
でアクセス可能な aaa
という値や、その型 (string 型) を取り出すことができます。
x := XXX{Fullname: "aaa", Number: 123}
rv := reflect.ValueOf(x)
v := rv.Field(0) // x.Fullname に対応する部分
fmt.Printf("%s : %q\n", v.Kind().String(), v.String())
// -> string : "aaa" と表示される
何が出来るようになるか
上記の情報を元に以下のようなコードが動作する encoding/excel
的な package の初期実装を作りました。
package main
import (
"io/ioutil"
"github.com/sago35/go-eexcel"
)
type XXX struct {
Name string `eexcel:"name"`
Number int `eexcel:"number"`
}
func main() {
x := XXX{Name: "aaa", Number: 123}
b, _ := eexcel.Marshal(x)
ioutil.WriteFile("out.xlsx", b, 0644)
// -> out.xlsx に出力される
}
上記コードで出力されるファイルは以下のようになります。
コードは以下にあります。
まとめ
reflect を使ったコード例と実装例を示しました。
Go において reflect は避けて通ることもできますが、触ってみるととても強力で面白いです。
reflect package を使ったことが無い人は是非使ってみてください。