Question

How do you unmarshal data into a map?

The JSON is a list of objects and I want to convert this into a map during the unmarshaling. However, it appears that initializing the map creates a new object that isn't returned correctly.

type foo struct {
    Id string `json:"id"`
    Name string `json:"name"`
}

type fooList []*foo
type fooMap map[string]*foo

func (f fooMap) UnmarshalJSON(data []byte) error {
    f := fooMap{} // required since map is not initialized

    var results fooList
    if err := json.Unmarshal(data, &results); err != nil {
        return err
    }

    for i := 0; i < len(results); i++ {
        result := results[i]
        f[result.Id] = result
    }

    return nil
}

I've checked to make sure there are no errors and that the map contains the correct values at the end of the Unmarshal function, but when I look at the object that the results should have been unmarshalled into, it is empty. Here is how I'm unmarshaling the parent object which is a slice of structs that contain fooMaps.

type bar struct {
    Foos fooMap `json:"foos"`
}

type bars []*bar

...

var results bars
if err := json.Unmarshal(data, &results); err != nil {
    return err
}

// here results contains a single bar item (which is correct) but the Foos
// property does not exist at all

I also tried changing Foos to be of type *fooMap and then updated the unmarshal function to be func (f *fooMap) UnmarshalJSON(...) but then the unmarshal function is never called.

Was it helpful?

Solution

This seems very over-complicated, and there's a couple confusing parts here.

  • Do you really need the intermediary fooList during UnmarshalJSON? This makes the json representation look different than the types you define, which definitely confused me at first.
  • At least here, there's no reason to be defining your own types for everything, and it's making the code very hard to read, as it hides the structure behind another layer of indirection.

So the crux of your problem is that the fooMap method does not have a pointer receiver. This makes it impossible to assign a non-nil map to the receiver from within your method. You do need a pointer receiver, which you need to dereference on each index operation. In your code you're simply assigning the map data structure to a temporary copy of the receiver (in a non-valid way I might add).

func (f *fooMap) UnmarshalJSON(data []byte) error {
    *f = make(map[string]*foo) // required since map is not initialized

    var results fooList
    if err := json.Unmarshal(data, &results); err != nil {
        return err
    }

    for i := 0; i < len(results); i++ {
        result := results[i]
        (*f)[result.Id] = result
    }

    return nil
}

And with my best guess of your json: http://play.golang.org/p/WbwZJxphTj

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top