GAE/Goを書いている時によく遭遇するので、リフレクションでかっこよく解決した。
やりたいこと
// いろんな型の構造体(のポインタ)が混ざったスライス
list := []interface{}{
&Dog{CanOte: true},
&Cat{NowSleeping: true},
&Shark{StarringMovie: true},
&Dog{CanOte: false},
}
// 型
type (
Animals struct {
Dogs []*Dog
Cats []*Cat
Sharks []*Shark
}
Dog struct {
CanOte bool
}
Cat struct {
NowSleeping bool
}
Shark struct {
StarringMovie bool
}
)
型を見て適切なフィールドにappendをしていきたい。
- DogをAnimals.Dogsにappend
- CatをAnimals.Catsにappend
- SharkをAnimals.Sharksにappend
同じ挙動をするコードはこれ。
func appendEachObjects(animals Animals, list []interface{}) {
for _, l := range list {
switch m := l.(type) {
case *Dog:
animals.Dogs = append(animals.Dogs, m)
case *Cat:
animals.Cats = append(animals.Cats, m)
case *Shark:
animals.Sharks = append(animals.Sharks, m)
}
}
}
リフレクションで解決するには
さて、いちいちcaseを増やしていくのはだるいのでリフレクションで解決する方法を考えた。
func appendEachObjects(animals Animals, list []interface{}) Animals {
defer func() {
if e := recover(); e != nil {
fmt.Printf("panic: %+v\n", e)
fmt.Println("DataObjectsのフィールドの型がポインタのSliceではないと思います")
}
}()
for _, i := range list {
hpv := reflect.ValueOf(&animals)
t := hpv.Elem().Type()
n := t.NumField()
for fieldIndex := 0; fieldIndex < n; fieldIndex++ {
field := t.Field(fieldIndex)
fieldType := field.Type
if fieldType.Kind() != reflect.Slice {
continue
}
match := fieldType.Elem().Elem() == reflect.TypeOf(i).Elem()
if !match {
continue
}
l := reflect.Append(hpv.Elem().Field(fieldIndex), reflect.ValueOf(i))
hpv.Elem().Field(fieldIndex).Set(l)
}
}
return animals
}
func Test_ObjectAppendEach(t *testing.T) {
animals := Animals{}
list := []interface{}{
&Dog{CanOte: true},
&Cat{NowSleeping: true},
&Shark{StarringMovie: true},
&Dog{CanOte: false},
}
actual := appendEachObjects(animals, list)
if len(actual.Dogs) != 2 {
t.Fatalf("犬が2匹ではなく%d", len(actual.Dogs))
}
if len(actual.Cats) != 1 {
t.Fatalf("猫が1匹ではなく%d", len(actual.Cats))
}
if len(actual.Sharks) != 1 {
t.Fatalf("サメが1匹ではなく%d", len(actual.Sharks))
}
if actual.Cats[0].NowSleeping != true {
t.Fatalf("actual.Cats[0].NowSleepingがTrueではなく%T", actual.Cats[0].NowSleeping)
}
}