A common problem that Go developers come up against is how to process interfaces.
They typically find themselves in a situation where their code has received an interface{}
and it’s not clear that the interface has the characteristics that the code is designed to deal with.
Someone on IRC asked how to decode the example that they had created. They were trying to conditionally unmarshal Yaml, that is, they wanted to detect if the Yaml they were being passed had a for
field, or not, and unmarshal the yaml into the correct struct.
I mean, it’s not the ideal situation to be in, but I’m not here to judge :)
The way to deal with interface{}
is to type assert every time you are dealing with what is an interface{}, type conversion (casting) removes that need, but more often an assertion is the better approach.
This is demonstrated in the code below, the example["roles"]
is type asserted as being an[]interface{}
in the loop, if this isn’t done the runtime will assume that the example["roles"]
is an `interface{}.
The next time example["roles"]
is seen, it is again type asserted as being []interface{}
, because, again, the runtime will think it is a interface{}
, and then the [idx]
can be applied. The .(type)
expression is what’s being selected on.
When v
is accessed, it is treated as a map[interface{}]interface{}, because of the select statement assigning a type converted version of the interface{}
, and the “for” key can be checked.
package main
import (
"fmt"
)
func main() {
example := map[string]interface{}{
"name": "x",
"default": map[string]interface{}{
"name": "y",
},
"roles": []interface{}{
map[interface{}]interface{}{
"name": "z",
"for": map[interface{}]interface{}{
"range": "it",
},
},
},
}
// Check if the `roles` key exists in example.
if _, ok := example["roles"]; ok {
// Loop over the []interface{}, note the type assertion.
for idx := range example["roles"].([]interface{}) {
// switch on the type of interface{}, note the type assertion has to be done again.
switch v := example["roles"].([]interface{})[idx].(type) {
// If the interface{} type is a map[interface{}]interface{}.
case map[interface{}]interface{}:
// v has been type converted by the switch statement
if _, ok := v["for"]; ok {
fmt.Println("For present")
}
}
}
}
}
At each point using the interface, there has to be an explicit instruction to the compiler how the interface{}
is to be treated, except when the interface{}
has been converted.
For all the dynamism and versatility of the interface{}
type, using them means we have to specify the type we expect the interface{}
to be (and the interface{}
must be that type). We can write code that decides how to behave, depending on the underlying type, but the interface{}
has been changed in its appearance so we can see its true self.