使用反射的过程将结构字段绑定到命令行标志值

发布于 2025-02-14 02:01:09 字数 1774 浏览 1 评论 0原文

我有几个配置结构,我想自动将其解析到接受的命令行标志中(允许用户通过CLI覆盖它们)。鉴于结构可能会随着时间的流逝而发展,并且其中一个结构是接口{},反射似乎是正确的方法。我只需要解析字符串,INT和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

所以,我的具体问题是

  • “有没有办法为float64s做这项工作?”,

我稍微更广泛的问题

  • 是不需要不安全的指针铸造的反射方法更好?”

我更喜欢完全尊重类型系统,但是如何通过反思做到这一点并不明显。另一种选择似乎是代码生成,我想避免使用,但是如果需要,可以纠缠。

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

  • "Is there a way to make this work for float64s?",

and my slightly broader question is

  • "Is there a better approach with reflection that wouldn't require the unsafe pointer casting?"

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

痞味浪人 2025-02-21 02:01:09

如果我正确理解您的要求,则无需使用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/pplay/p/1f2kyo0cbuj

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文