I am trying to process some data retrieved from mongodb (mgo).
Unfortunately I am unable to assert the correct type for a list of strings. The function I am working on is the following:
func generate_version_histogram(userStats []interface{}) map[string]int {
var histogram map[string]int
for _, _u := range userStats {
u := _u.(bson.M)
for _, version := range (u["v"]).([]string) {
if _, alreadyhere := histogram[version]; alreadyhere {
histogram[version] += 1
} else {
histogram[version] = 1
}
}
}
return histogram
}
Unfortunately I am getting this following run-time panic:
interface conversion: interface is []interface {}, not []string
Any idea on why this is happening? How can I retrieve those strings?
This is a common mistake with Go.
The reason is as follows: in Go []interface{}
is not an interface, it's a slice type, whose elements are each the interface{}
type.
Because each element is a interface{}
, rather than, say, an int
or Foo
, more memory is taken up by each element (interface{}
needs to store the underlying type, and the value contained). Therefore, it's not possible to directly convert a []interface
value into a []string
or []T
value.
How do you convert []interface{}
into []string
, then?
The solution is quite simple — you convert each element.
package main
import "fmt"
func main() {
foo := []interface{}{"a", "b", "c"}
// we want to convert foo to a []string
out := []string{}
for _, v := range foo {
// using a type assertion, convert v to a string
out = append(out, v.(string))
}
fmt.Println(out)
}
Runnable example here.
A []interface{}
will never be a []string
. A slice has a backing array, and those elements have to be of a certain size. This may or may not be exactly correct depending on implementation details, but each element of an []interface{}
will contain an interface{}
. If all of these interface{}
are "really" strings, they won't be the strings themselves but rather a wrapper over the string or a pointer to it. Thus you have to convert each individual element of a []interface{}
yourself.
stringSlice := make([]string, len(u["v"]))
for i,raw := range u["v"] {
str,ok := raw.(string)
if !ok {
// Something is wrong, not a string like we expected
}
stringSlice[i] = str
}