2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GoAdvent Calendar 2021

Day 10

Go 言語の reflect : 最初の一歩

Last updated at Posted at 2021-12-10

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 に出力される
}

上記コードで出力されるファイルは以下のようになります。

image.png

コードは以下にあります。

まとめ

reflect を使ったコード例と実装例を示しました。
Go において reflect は避けて通ることもできますが、触ってみるととても強力で面白いです。
reflect package を使ったことが無い人は是非使ってみてください。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?