Getting invalid operation: mymap["title"] (type interface {} does not support indexing) when trying to index a map

I have data that's in a map, and I want to index into the map by key to get a value.

mdi, err := page.Metadata()
fmt.Println(mdi["title"])

However I keep getting the error message invalid operation: mdi["title"] (type interface {} does not support indexing). I am confused, because the data is a map and I should be able to index into it to get the value. In case the type wasn't clear, I also tried to cast the value to a string:

title, ok := mdi["title"].(string)
checkOk(ok)
fmt.Println(title)

However, I got the same error message. What am I doing wrong?


Solution 1:

The data type here was the key. mdi was not actually a map, but an interface{}, which could be anything - a map, a string, an int. You need to assert it to a map with expected key/value types first, or do the awkward case switch outlined in JSON and Go.

mdi, err := page.Metadata()
md, ok := mdi.(map[string]interface{})
fmt.Println(md["title"])

Solution 2:

I'm not sure if you have control over what page.Metadata does, but if you do, a better way is available for this situation. Say your code looks like this:

package main

import (
   "encoding/json"
   "log"
)

func Metadata() (interface{}, error) {
   y := []byte(`{"metadata": {"title": "Stack Overflow"}}`)
   var m map[string]interface{}
   e := json.Unmarshal(y, &m)
   if e != nil {
      return nil, e
   }
   return m["metadata"], nil
}

func main() {
   m, e := Metadata()
   if e != nil {
      log.Fatal(e)
   }
   s := m["title"]
   println(s == "Stack Overflow")
}

You'd get the same error you got. But you can change it to this:

type Map map[string]interface{}

func (m Map) M(s string) Map {
   return m[s].(map[string]interface{})
}

func (m Map) S(s string) string {
   return m[s].(string)
}

func Metadata() (Map, error) {
   y := []byte(`{"metadata": {"title": "Stack Overflow"}}`)
   var m Map
   e := json.Unmarshal(y, &m)
   if e != nil {
      return nil, e
   }
   return m.M("metadata"), nil
}

func main() {
   m, e := Metadata()
   if e != nil {
      log.Fatal(e)
   }
   s := m.S("title")
   println(s == "Stack Overflow")
}

Then whenever you need to index, you just call the appropriate method depending on what you are wanting to return. You can also add Slice if need be []interface{}, and further methods from what I put, for example if you need to return integer. Finally, if you want to check if Map contains key, you can do this:

if m["title"] != nil {
   s := m.S("title")
   println(s == "Stack Overflow")
}