Search code examples
pointersgocommand-line-interfacereflect

Reflection struct field.Set with a Flag pointer value


I have a bunch of flags parsed, and I'm then trying to assign those values to fields in a struct, but I'm struggling to get a parsed flag value set into the struct because I can't type assert it or cast it.

Here is a snippet of the code I have. It's not important to worry too much about the IterFields function, basically the third argument is called for each field in the struct...

Note: there are comments in the code below which highlight the error(s).

    flag.Parse()

    IterFields(st, v, func(field reflect.Value, sf reflect.StructField) {
        flag.VisitAll(func(f *flag.Flag) {
            if f.Name == strings.ToLower(sf.Name) || f.Name == sf.Tag.Get("short") {
                fmt.Printf("%+v, %T\n", f.Value, f.Value)
                // PRINTS: true, *flag.boolValue
                
                if v, ok := f.Value.(bool); ok {
                    fmt.Println("ok")
                } else {
                    fmt.Println("not ok")
                }
                // ERROR: impossible type assertion: bool does not implement flag.Value (missing Set method)
                
                field.Set(reflect.ValueOf(f.Value))
                // PANIC: value of type *flag.boolValue is not assignable to type bool
            }
        })
    })

Solution

  • f.Value is an interface type flag.Value abstracting all kinds of flag values. As your code indicates, it's not of type bool but some non-exported *flag.boolValue. You shouldn't be concerned about its dynamic type.

    You may use the Value.String() method to get its value as a string, which will be either "false" or "true" for bool types, you may use simple comparison to obtain a bool from it like f.Value.String() == "true".

    But a better approach would be: all flag.Value values originating from the flag package also implement flag.Getter which also has a Get() method that will directly return a bool value in case of a bool flag (wrapped in interface{} of course). Just use that:

    field.Set(reflect.ValueOf(f.Value.(flag.Getter).Get()))
    

    The above works for fields of any type (given that the flag's value type is assignable to the field's type).

    For bool fields only, alternatively you may also use:

    field.SetBool(f.Value.(flag.Getter).Get().(bool))