How to decode json strings to sync.Map instead of normal map in Go1.9?

Viewed 2521

I can decode json strings to a map with go language like this:

func main(){
  date := []byte(`{"127.1":{"host":"host1","list":["list123","list456"]},"127.2":{"host":"host2","list":["list223","list256"]}}`)
  var x interface{}
  json.Unmarshal(date, &x)
  t := x.(map[string]interface{})
  var aa []interface{}
  aa = (t["127.2"].(map[string]interface{})["list"])
  for _, v := range aa {
     fmt.Println(v.(string))
  }
}

but I wonder how to decode it to a sync.Map in Go1.9. I have tried many ways but failed, can anyone help me?

I tried like this:

    func main(){
      date := []byte(`{"127.1":{"host":"host1","list":["list123","list456"]},"127.2":{"host":"host2","list":["list223","list256"]}}`)
      var x interface{}
      json.Unmarshal(date, &x)
      t := x.((sync.Map)[string]interface{})  //compile error
}

Also I tried like this:

    func main(){
      date := []byte(`{"127.1":{"host":"host1","list":["list123","list456"]},"127.2":{"host":"host2","list":["list223","list256"]}}`)
      var x sync.Map
      json.Unmarshal(date, &x)
      fmt.Println(x) // but the map has nothing
}
3 Answers

You cannot directly unmarshal into a sync.Map, because sync.Map has no exported fields (so the Unmarshaler doesn't have any way to store data in it), and it doesn't implement the json.Unmarshaler interface.

So you'll have to handle this yourself, probably by including a sync.Map in a type of your own, and implementing json.Unmarshaler on that type:

type Foo struct {
    sync.Map
}

func (f *Foo) UnmarshalJSON(data []byte) error {
    var tmpMap map[string]interface{}
    if err := json.Unmarshal(data, &tmpMap); err != nil {
        return err
    }
    for key, value := range tmpMap {
        f.Store(key, value)
    }
    return nil
}

In case you need a snippet to do it another way around

func (f Foo) MarshalJSON() ([]byte, error) {
    tmpMap := make(map[YourTypeOfKey]YourTypeOfValue)
    f.Range(func(k, v interface{}) bool {
        tmpMap[k.(YourTypeOfKey)] = v.(YourTypeOfValue)
        return true
    })
    return json.Marshal(tmpMap)
}

Just adding to @jakub great answer about MarshalJSON, you can also have a generic function that doesn't check the type (since json doesn't care either)

func MarshalJSON(m *sync.Map) ([]byte, error) {
    tmpMap := make(map[interface{}]interface{})
    m.Range(func(k, v interface{}) bool {
        tmpMap[k] = v
        return true
    })
    return json.Marshal(tmpMap)
}

This could take any kind of sync.Map and just Marshal it.

To add to the UnMarshal function from @Flimzy's answer, you can play a bit with the types by omitting the string part to make it more generics (pun intended, cry in corner):

func UnmarshalJSON(data []byte) (*sync.Map, error) {
    var tmpMap map[interface{}]interface{}
    m := &sync.Map{}

    if err := json.Unmarshal(data, &tmpMap); err != nil {
        return m, err
    }

    for key, value := range tmpMap {
        m.Store(key, value)
    }
    return m, nil
}

In general it is good to play with the same Types when doing this sort of manipulation, because we are lazy and we don't want to rewrite functions ;)

Related