fny
fny

Reputation: 33587

Extracting Generic Struct Values by Reflection

I'm trying to extract all of the values for a struct into a string slice.

func structValues(item Item) []string {
    values := []string{}
    e := reflect.ValueOf(&item).Elem()

    for i := 0; i < e.NumField(); i++ {
        fieldValue := e.Field(i).Interface()
        values = append(values, fmt.Sprintf("%#v", fieldValue))
    }
    return values
}

I'd like to use this function with any struct, so I thought I could just change the type signature to func structValues(item interface{}) but then I got a panic:

panic: reflect: call of reflect.Value.NumField on interface Value

Working example: https://repl.it/@fny/stackoverflow61719532

Upvotes: 0

Views: 1098

Answers (1)

torek
torek

Reputation: 489153

I'd like to use this function with any struct ...

You can do this, but note that it gives up type-safety. Moreover, the only way to do this is to allow a call with any type, not just any type that is some structure type, so you have to check that what you got was in fact some struct type:

func structValues(item interface{}) {
    if reflect.ValueOf(item).Kind() != reflect.Struct {
        ... do something here ...
    }

Having made that check—or deferring it slightly, or omitting it to allow reflect to panic instead—you then need to replace reflect.ValueOf(&item).Elem() with the simpler reflect.ValueOf(item).

If you wish to allow pointers to structures as well as actual structures, you can make that happen pretty simply by using reflect.Indirect first. The result is:

func structValues(item interface{}) []string {
    e := reflect.Indirect(reflect.ValueOf(item))
    if e.Kind() != reflect.Struct {
        panic("not a struct")
    }
    values := []string{}
    for i := 0; i < e.NumField(); i++ {
        fieldValue := e.Field(i).Interface()
        values = append(values, fmt.Sprintf("%#v", fieldValue))
    }
    return values
}

Leave out the reflect.Indirect if you want to make sure that callers do their own indirection when they have a pointer.

(Note that the panic here is not very friendly. If you want proper debugging, consider either just printing the struct directly with %v or %#v, or for something much more thorough, the spew package.)

Complete example here on the Go Playground uses your type Item struct from your own link.

Upvotes: 2

Related Questions