golang
reflection

golangでStructを動的に作る方法。

どのような使い道があるのかはさておき、reflectパッケージを使って動的に任意のFieldを持つStructを作ることができる。

package main

import (
    "fmt"
    "reflect"
)

type StructBuilder struct {
    field []reflect.StructField
}

func NewStructBuilder() *StructBuilder {
    return &StructBuilder{}
}

func (b *StructBuilder) AddField(fname string, ftype reflect.Type) {
    b.field = append(
        b.field,
        reflect.StructField{
            Name: fname,
            Type: ftype,
        })
}

func (b *StructBuilder) Build() Struct {
    strct := reflect.StructOf(b.field)
    index := make(map[string]int)
    for i := 0; i < strct.NumField(); i++ {
        index[strct.Field(i).Name] = i
    }
    return Struct{strct, index}
}

type Struct struct {
    strct reflect.Type
    index map[string]int
}

func (s *Struct) NewInstance() *Instance {
    instance := reflect.New(s.strct).Elem()
    return &Instance{instance, s.index}
}

type Instance struct {
    internal reflect.Value
    index    map[string]int
}

func (i *Instance) Field(name string) reflect.Value {
    return i.internal.Field(i.index[name])
}

func (i *Instance) SetString(name, value string) {
    i.Field(name).SetString(value)
}

func (i *Instance) SetBool(name string, value bool) {
    i.Field(name).SetBool(value)
}

func (i *Instance) SetInt(name string, value int) {
    i.Field(name).SetInt(int64(value))
}

func (i *Instance) SetFloat(name string, value float64) {
    i.Field(name).SetFloat(value)
}

func (i *Instance) Value() interface{} {
    return i.internal.Interface()
}

func (i *Instance) Pointer() interface{} {
    return i.internal.Addr().Interface()
}

func main() {
    b := NewStructBuilder()
    b.AddField("Name", reflect.TypeOf("")) //型情報が欲しいだけなので、reflect.TypeOf()の入力値はなんでもいい。
    b.AddField("Age", reflect.TypeOf(123))
    person := b.Build()

    i := person.NewInstance()
    i.SetString("Name", "gopher")
    i.SetInt("Age", 8)

    fmt.Println(i.Value())   // -> {gopher 8}
    fmt.Println(i.Pointer()) // -> &{gopher 8}
}
  1. reflect.StructField{}でFieldを定義する。
  2. StructFieldをSliceでまとめたものをreflect.StructOf()に渡すとStructが生成される。
  3. このStructをreflect.New()に渡すとインスタンスができる。
  4. SetXXXで値をつめて
  5. Interface()Addr().Interface()でおなじみの形式に変換。

ORMなどのリフレクション前提のライブラリで使い道がある...かも

参考

  1. reflect - The Go Programming Language
  2. The Go Playground