Checking reflect.Kind on interface{} return invalid result

Issue

I have some question about Go reflect behaviour.

I have a slice named src defined in []interface{} type. I’d like to get the actual type of each element within the slice. This is what I did:

src := []interface{}{"noval", 0, nil}
srcType := reflect.ValueOf(src)

for i := 0; i < srcType.Len(); i++ {
    each := srcType.Index(i)

    if each.Interface() == nil {
        fmt.Println("value", each.Interface(), "is nil")
    } else {
        switch each.Kind() {
        case reflect.String:
            fmt.Println("value", each.Interface(), "is string")
        case reflect.Int, reflect.Int16:
            fmt.Println("value", each.Interface(), "is int")
        default:
            fmt.Println("value", each.Interface(), "is ?")
        }
    }
}

Output:

value noval is ?
value 0 is ?
value <nil> is nil

For some reason, the data type of element "noval" is not detected as string, hence the default block is executed within the switch.

Also the 0 value should be identified as reflect.Int but the default block is called again.

Can somebody enlighten me please, thank you in advance.

Solution

src is a slice whose element type is interface{}. So each element you obtain is of static type interface{}, so their “kind” will be reflect.Interface. Adding a new reflect.Interface case will reveal that:

case reflect.Interface:
    fmt.Println("value", each.Interface(), "is interface")

Output will be (try it on the Go Playground):

value noval is interface
value 0 is interface
value <nil> is nil

If you want the “wrapped” element in the interface, use Value.Elem():

} else if each.Kind() == reflect.Interface {
    switch each.Elem().Kind() {
    case reflect.String:
        fmt.Println("value", each.Interface(), "is string")
    case reflect.Int, reflect.Int16:
        fmt.Println("value", each.Interface(), "is int")
    default:
        fmt.Println("value", each.Interface(), "is ?")
    }
}

Then output will be (try it on the Go Playground):

value noval is string
value 0 is int
value <nil> is nil

Also note that you’re “switching” over the kind of the values, not over their actual types. What this means is that values of multiple types may end up in specific cases, like in this one:

type mystr string
src := []interface{}{"noval", 0, nil, mystr("my")}
srcType := reflect.ValueOf(src)

// ...

Output of this will be (try it on the Go Playground):

value noval is string
value 0 is int
value <nil> is nil
value my is string

The value mystr("my") is detected as string, because it is of string “kind”, but its type is not string but mystr. This may or may not be what you want. If you want to distinguish between a value of type string and mystr, then you should “switch” over the actual type of values like in this example:

} else if each.Kind() == reflect.Interface {
    switch each.Elem().Type() {
    case reflect.TypeOf(""):
        fmt.Println("value", each.Interface(), "is string")
    case reflect.TypeOf(0):
        fmt.Println("value", each.Interface(), "is int")
    case reflect.TypeOf(mystr("")):
        fmt.Println("value", each.Interface(), "is mystr")
    default:
        fmt.Println("value", each.Interface(), "is ?")
    }
}

Then output will be (try it on the Go Playground):

value noval is string
value 0 is int
value <nil> is nil
value my is mystr

As you can see "nova" is detected as a value of type string, and mystr("my") is properly detected as a value of type mystr.

Also note that for what you’re trying to do, you don’t need reflection, just use a type switch:

src := []interface{}{"noval", 0, nil}
for _, v := range src {
    switch v.(type) {
    case string:
        fmt.Println("value", v, "is string")
    case int:
        fmt.Println("value", v, "is int")
    case nil:
        fmt.Println("value", v, "is nil")
    default:
        fmt.Println("value", v, "is ?")
    }
}

Output (try it on the Go Playground):

value noval is string
value 0 is int
value <nil> is nil

Answered By – icza

Answer Checked By – Pedro (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.