26

I have such map:

Map := make(map[string]interface{})

This map is supposed to contain mapping from string to array of objects. Arrays can be of different types, like []Users or []Hosts. I populated this array:

TopologyMap["Users"] = Users_Array
TopologyMap["Hosts"] = Hosts_Array

but when I try to get an elements from it:

Map["Users"][0]

it gives an error: (type interface {} does not support indexing)

How can I overcome it?

Kenenbek Arzymatov
  • 8,439
  • 19
  • 58
  • 109

3 Answers3

18

You have to explicitly convert your interface{} to a slice of the expected type to achieve it. Something like this:

package main
    
import "fmt"
    
type Host struct {
    Name string
}

func main() {
    Map := make(map[string]interface{})
    Map["hosts"] = []Host{Host{"test.com"}, Host{"test2.com"}}
    
    hm := Map["hosts"].([]Host)
    fmt.Println(hm[0])
}

Playground link

Ullaakut
  • 3,554
  • 2
  • 19
  • 34
Vadyus
  • 1,299
  • 8
  • 19
  • yes, it's good, but is it possible to do it while populating `Map` to avoid additional assignments like this `hm := Map["hosts"].([]Host)`? – Kenenbek Arzymatov Nov 26 '17 at 12:30
  • Have suggested an edit to include the code in the actual question rather than just a link, as links can break. – Lou Jan 05 '22 at 17:35
9

First thing to be noted is the interface{} can hold any data type including function and struct or []struct. Since the error gives you :

(type interface {} does not support indexing)

It means that it holds no slice or no array values. Because you directly call the index in this case is 0 to an interface{} and you assume that the Map["Users"] is an array. But it is not. This is one of very good thing about Go it is statically type which mean all the data type is check at compiled time.

if you want to be avoid the parsing error like this:

panic: interface conversion: interface {} is []main.User, not []main.Host

To avoid that error while your parsing it to another type like Map["user"].([]User) just in case that another data type pass to the interface{} consider the code snippet below :

u, ok := myMap["user"].([]User)
if ok {
    log.Printf("value = %+v\n", u)
}

Above code is simple and you can use it to check if the interface match to the type you are parsing.

And if you want to be more general passing the value to your interface{} at runtime you can check it first using reflect.TypeOf() please consider this code :

switch reflect.TypeOf(myMap["user"]).String() {
case "[]main.User":
    log.Println("map = ", "slice of user")
    logger.Debug("map = ", myMap["user"].([]User)[0])

case "[]main.Host":
    log.Println("map = ", "slice of host")
    logger.Debug("map = ", myMap["user"].([]Host)[0])

}

after you know what's the value of the interface{} you can confidently parse it the your specific data type in this case slice of user []User. Not that the main there is a package name you can change it to yours.

Gujarat Santana
  • 9,854
  • 17
  • 53
  • 75
  • 2
    There is no reason to reach for reflection here. Use the two-value form of type assertions to avoid the panic: u, ok := myMap["user"].([]User) – Peter Nov 26 '17 at 13:22
  • Thank you for correcting me, you're right about that. I edit my answer now – Gujarat Santana Nov 26 '17 at 13:42
0

This is how I solved it for unstructured data. You have to parse to index string until you reach the end. Then you can print key value pairs.

yamlStr := `
input:
  bind: 0.0.0.0:12002
  interface: eth0
reaggregate: {}
versions: {}
output:
  destination: owl-opsw-sw-dev-4.opsw:32001
  interface: eth0
`

var obj map[string]interface{}
if err := yaml.Unmarshal([]byte(yamlStr), &obj); err != nil {
    // Act on error
}

// Set nested object to map[string] 
inputkv := streamObj["input"].(map[string]interface{})
for key, value := range inputkv {
    // Each value is an interface{} type, that is type asserted as a string
    fmt.Println(key, value.(string))
}

Result

bind 0.0.0.0:12002
interface eth0
MaxThom
  • 1,165
  • 1
  • 13
  • 20