Problems JSON-unmarshaling custom map type

1,552 views
Skip to first unread message

Jens Alfke

unread,
Mar 11, 2013, 5:05:27 PM3/11/13
to golan...@googlegroups.com
So, I’ve got a custom type based on a map:
type Set map[string]struct{}
I’ve implemented JSON marshaling/unmarshaling for it, so it can be stored in JSON as an array of strings:
func (set Set) MarshalJSON() ([]byte, error) {…}
func (set Set) UnmarshalJSON(data []byte) error { … }

Now I can unmarshal JSON into a struct containing a Set:

type Data struct {
Names Set
}
var data Data
json.Unmarshal(jsonBytes, &data) // crash!

Unfortunately this fails with a null-pointer exception down in the UnmarshalJSON method, because the JSON decoder calls that method with a receiver (the ‘set’ variable) that’s nil. So when UnmarshalJSON tries to store a value into it, it crashes.

Is this a bug in the JSON package?

I can’t find any good workaround.
* There’s nothing I can do inside the UnmarshalJSON method, because even if I instantiate a map in it, there’s no way to pass that back to the caller, so it’s lost.
* I can pre-initialize the struct member, i.e. by adding “data.Names = Set{}” before the Unmarshal call. This leads to the correct result. But it’s awkward having to pre-initialize the unmarshaled object, and worse, now I have no way to detect whether the JSON contains a “names” field at all, since data.Names will never be nil after the unmarshaling.

–Jens

Jens Alfke

unread,
Mar 11, 2013, 5:35:42 PM3/11/13
to golan...@googlegroups.com
Shortly after sending my question I had a brainstorm and figured out the answer. It has to do with that subtlety of Go regarding method regarding pointer vs. non-pointer receiver types. (Coupled with the subtlety of map types being implicitly pointers without needing a *.) The solution was to make UnmarshalJSON take a *Set, not a Set, as a receiver. With that level of indirection I am able to store a new map.

For reference, here are my working marshal/unmarshal functions. I’ve bolded the changes: UnmarshalJSON takes a pointer to a map, and if the pointer points to a nil map, I create a new map and store it there.

func (set Set) MarshalJSON() ([]byte, error) {
return json.Marshal(set.ToArray())
}

func (setPtr *Set) UnmarshalJSON(data []byte) error {
if *setPtr == nil {
*setPtr = Set{}
}
set := *setPtr
var names []string
if err := json.Unmarshal(data, &names); err != nil {
return err
}
for _, name := range names {
set[name] = struct{}{}
}
return nil
}

—Jens

ory...@gmail.com

unread,
Jun 10, 2015, 1:08:15 AM6/10/15
to golan...@googlegroups.com
you just save my day! :)
Reply all
Reply to author
Forward
0 new messages