This solution had me literally working on-again off-again for a full week.
One of the main reasons it took so long was I was trying to write a wrapper to simplify because I find the reflect
package in Go so unintuitive and hard-to-work-with. But it turns out that trying to write a wrapper around an unintuitive and hard-to-work-with package simply created for me two problems (much like trying to use regular expressions, but I digress…)
So I paired it back to the basics and figured out the problem I was having. I had declared the data structure as map[string]info
instead of using a pointer to the struct, e.g.: map[string]*info
.
For those playing along at home, here is code that illustrates the use-case and my solution, or you can jump to the Go playground to see it in action. (You can also watch a screencast I prepared for the good folks at JetBrains in hopes they will add features to improve debugging reflection in their GoLand IDE for Go):
package main
import (
"encoding/json"
"fmt"
"github.com/mikeschinkel/go-only"
"reflect"
"testing"
)
var defaultChild = child{
Key: "default",
}
var defaultInfo = info{
Data: "default-data",
Child: &defaultChild,
}
type child struct {
Key string
}
type info struct {
Data string
Child *child
}
var testInfo = map[string]*info{
"test1": {
Data: "foo",
},
"test2": {
Child: &child{Key:"bar"},
},
}
const indent = " "
func TestReflectAssign(t *testing.T) {
/* 1 */ d := testInfo["test1"].Data ; p(d);
/* 2 */ d = testInfo["test2"].Data ; p(d);
/* 3 */ v := reflect.ValueOf(&testInfo) ; p(v);
/* 4 */ v = v.Elem() ; p(v);
/* 5 */ k := v.MapKeys() ; p(k);
/* 6 */ i := k[0] ; p(i);
/* 7 */ di := reflect.ValueOf(&defaultInfo) ; p(di);
/* 8 */ de := di.Elem() ; p(de);
for _,mi := range k {
/* 9+4(n-1) */ f := v.MapIndex(mi) ; p(f)
/* 10+4(n-1) */ fe := f.Elem() ; p(fe)
/* 11+4(n-1) */ ff := fe.Field(0) ; p(ff)
if ff.String() == "" {
ff.SetString(de.Field(0).String())
}
/* 12+4(n-1) */ ff = fe.Field(1) ; p(ff);
if ff.IsNil() {
ff.Set(de.Field(1))
}
}
/* n */ p(testInfo)
}
//
// Everything below here is just used to output Type, Kind and Value
// for each of the intermediate steps captured in the variables.
//
var counter = 0
func p(i interface{}) {
counter++
v := reflect.ValueOf(i)
if v2, k := i.(reflect.Value); k {
v = v2
}
t:= v.Kind().String()
k:= v.Kind().String()
var nl string
if t == k {
k = ""
nl = "\n\n"
} else {
k = fmt.Sprintf("%sKind: %s\n",
indent,
t)
}
fmt.Printf("%d.) Type: %s\n%s%sValue: %#v%s",
counter,
t,
k,
indent,
i,
nl)
x := byType(i)
if x != nil {
fmt.Printf("%s%s\n\n",
indent,
toJson(x))
}
}
func byType(i interface{}) (x interface{}) {
for range only.Once {
if v, k := i.([]reflect.Value); k {
var is []interface{}
for _, s := range v {
is = append(is, s.Interface())
}
x = is
break
}
if v, k := i.(reflect.Value); k {
i = v.Interface()
if v.Kind() == reflect.String {
break
}
x = i
break
}
x = byType(reflect.ValueOf(i))
}
return x
}
func toJson(i interface{}) string {
j,err := json.MarshalIndent(i, indent," ")
if err != nil {
println(err.Error())
}
return string(j)
}
Hope this helps someone avoid the week of unproductive head banging I went through to get to this.