Unmarshal nested map with int keys

Viewed 279

If I have a map, I can Marshal it no problem:

package main

import (
   "encoding/json"
   "os"
)

type object map[int]interface{}

func main() {
   obj := object{
      1: "one", 2: object{3: "three"},
   }
   buf, err := json.Marshal(obj)
   if err != nil {
      panic(err)
   }
   os.Stdout.Write(buf) // {"1":"one","2":{"3":"three"}}
}

However I want to do the reverse. I tried this:

package main

import (
   "encoding/json"
   "fmt"
)

func main() {
   buf := []byte(`{"1":"one","2":{"3":"three"}}`)
   var obj map[int]interface{}
   json.Unmarshal(buf, &obj)
   // map[int]interface {}{1:"one", 2:map[string]interface {}{"3":"three"}}
   fmt.Printf("%#v\n", obj)
}

Only the top level has the correct type. Is it possible to do what I am wanting?

1 Answers

JSON keys are never anything but strings, that's how the spec is defined, and you can see that in the output of you marshal. So when you try the reverse with interface{} as the top level map's value type, the type information for the nested objects is lost. You'd need a custom map type that implements UnmarshalJSON to be able to do what you want.

For example:

type IntKeyMap map[int]interface{}

func (m *IntKeyMap) UnmarshalJSON(data []byte) error {
    raw := map[int]json.RawMessage{}
    if err := json.Unmarshal(data, &raw); err != nil {
        return err
    }
    for k, v := range raw {
        // check if the value is a nested object
        if len(v) > 0 && v[0] == '{' && v[len(v)-1] == '}' {
            // The following assumes that the nested JSON object's
            // key strings represent integers, if that is not the
            // case this block will fail.
            obj := IntKeyMap{}
            if err := json.Unmarshal([]byte(v), &obj); err != nil {
                return err
            }
            (*m)[k] = obj
        } else {
            var i interface{}
            if err := json.Unmarshal([]byte(v), &i); err != nil {
                return err
            }
            (*m)[k] = i
        }
    }
    return nil
}

https://go.dev/play/p/lmyhqD__Uod

Related