JSON Unmarshalling and Marshalling Custom Types

If you’ve created your own custom type and want to use it in your JSON unmarshalling, you can do so by implementing the Unmarshaler interface. This works fine if your custom type is a struct but what if your custom type was a scalar or a map?

If you had a struct you would normally write the JSON tags as normal (this struct is a bit goofy looking - hence why I looked into unmarshalling directly into a map[string]string as you’ll see soon):

type KeyVals struct {
    KeyVals map[string]string `json:"keyvals,omitempty"`
}

And that would have been the end of it.

But if you had a scalar, a map, or any other custom type you would have to implement the Unmarshaler and perhaps also the Marshaler interface yourself.

My client had some code looking like this that I had to unmarshal some JSONB from PostgreSQL into:

type KeyVals map[string]string

The naive implementation is to use the json.Unmarshal and json.Marshal functions respectively like this:

func (kvs *KeyVals) MarshalJSON() ([]byte, error) {
	return json.Marshal(kvs)
}

func (kvs *KeyVals) UnmarshalJSON(data []byte) error {
	return json.Unmarshal(data, &kvs)
}

This however results in a stack overflow because of MarshalJSON and UnmarshalJSON being recursively and infinitely.

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0201614d0 stack=[0xc020160000, 0xc040160000]
fatal error: stack overflow

Solution

The solution is to create an intermediary variable to break the infinite loop. In UnmarshalJSON we also have to alias the type.

type KeyVals map[string]string

func (kvs *KeyVals) MarshalJSON() ([]byte, error) {
	v := map[string]string(*kvs)

	return json.Marshal(v)
}

func (kvs *KeyVals) UnmarshalJSON(data []byte) error {
	type temp *KeyVals
	t := temp(kvs)

	return json.Unmarshal(data, &t)
}