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