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)
}
