使用reflect按程序将结构体字段绑定到命令行标志值

问题描述 投票:0回答:1
我有几个配置结构,我想将它们自动解析为可接受的命令行标志(以允许用户通过 CLI 覆盖它们)。鉴于这些结构可能会随着时间的推移而演变,并且其中一个结构是

interface{}

,反射似乎是正确的方法。我只需要解析字符串、整数和 float64。我已经完成以下工作:

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) } } } }
但是当我取消注释 float64 块时,我感到恐慌:

panic: reflect.Value.UnsafeAddr of unaddressable value goroutine 1 [running]: reflect.Value.UnsafeAddr(...) /usr/local/go/src/reflect/value.go:2526
所以,我的具体问题是

    “有没有办法让它适用于 float64?”,
我的更广泛的问题是

    “是否有更好的反射方法,不需要不安全的指针转换?”
我更愿意完全尊重类型系统,但如何通过反射来做到这一点并不明显。另一种选择似乎是代码生成,我想避免这种情况,但如果需要的话可以争论。

go struct panic go-cobra pointer-conversion
1个回答
1
投票
如果我正确理解了您的要求,则无需使用

unsafe

。要获取指向字段的指针,您只需使用 
Value.Addr
 方法和 
类型断言 来获取具体类型。

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))) } } }

https://go.dev/play/p/1F2Kyo0cBuj

© www.soinside.com 2019 - 2024. All rights reserved.