I have a several configuration structures that I want to automatically parse into accepted command line flags (to allow a user to override them via CLI). Given that the structures are likely to evolve over time, and one of the structures is an interface{}
, reflection seems to be the correct approach. I only need to parse strings, ints, and float64s. I've gotten the following working:
func ReconGenerateFlags(in *ReconConfig, cmd *cobra.Command) {
for _, f := range reflect.VisibleFields(reflect.TypeOf(*in)) {
group_name := f.Name
v := reflect.ValueOf(in).Elem().FieldByName(f.Name).Elem() // Return the concrete substructures pointed to by "in"
sub_fields := reflect.VisibleFields(v.Type())
for _, sub_f := range sub_fields {
tag_name := sub_f.Name
sub_v := v.FieldByName(tag_name)
full_flag_name := strings.ToLower(group_name) + "." + strings.ToLower(tag_name)
switch s := sub_v.Type().String(); s {
case "string":
ptr := (*string)(unsafe.Pointer(sub_v.UnsafeAddr()))
cmd.Flags().StringVar(ptr, flag_name, "", "")
case "int":
ptr := (*int)(unsafe.Pointer(sub_v.UnsafeAddr()))
cmd.Flags().IntVar(ptr, flag_name, 0, "")
//case "float64":
// ptr := (*float64)(unsafe.Pointer(sub_v.UnsafeAddr()))
// cmd.Flags().Float64Var(ptr, flag_name, 0.0, "")
default:
fmt.Printf("unrecognized type in config setup: %s\n", s)
}
}
}
}
But when I uncomment the float64 block I get a panic:
panic: reflect.Value.UnsafeAddr of unaddressable value
goroutine 1 [running]:
reflect.Value.UnsafeAddr(...)
/usr/local/go/src/reflect/value.go:2526
So, my concrete question is
and my slightly broader question is
I'd much prefer to fully respect the type system, but it's not obvious how to do this with reflection. The other alternative seems like it would be with code generation, which I'd like to avoid, but can wrangle if needed.
If I understood your requirements correctly then there's NO need to use unsafe
. To get a pointer to a field you can just use the Value.Addr
method and type assertions to get the concrete type.
func GenerateFlags(in interface{}, fs *flag.FlagSet, names []string) {
rv := reflect.ValueOf(in)
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct {
return // exit if not pointer-to-a-struct
}
rv = rv.Elem()
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
sf := rt.Field(i)
fv := rv.Field(i)
name := strings.Join(append(names, strings.ToLower(sf.Name)), ".")
switch fv.Type() {
case reflect.TypeOf(string("")):
p := fv.Addr().Interface().(*string)
fs.StringVar(p, name, "", "")
case reflect.TypeOf(int(0)):
p := fv.Addr().Interface().(*int)
fs.IntVar(p, name, 0, "")
case reflect.TypeOf(float64(0)):
p := fv.Addr().Interface().(*float64)
fs.Float64Var(p, name, 0, "")
default:
names := append([]string{}, names...)
GenerateFlags(fv.Interface(), fs, append(names, strings.ToLower(sf.Name)))
}
}
}