Custom JSON serialisation in golang to fix a Kong bug

I wrote an open source terraform provider for kong towards the end of last year.  The provider gave me a chance to write some go code and also the chance to do an open source project.  It seems to be pretty popular which is cool as it is nice to have written something that people are finding useful.

Recently someone logged a really detailed (thanks) github issue against a bug they found with the terraform provider.  It turned out the cause of this bug was the way that Kong was returning JSON objects when one of the fields was an empty list.  If a list property on the kong API object is set to [] then instead of returning [] Kong returns {}.

I wanted to fix this in a generic way for all list fields on the API object and also fix it for every operation on API (GET, POST, PUT etc).  Ie the ideal fix would see the code only being needed in a single place.  So to fix this I first wrote a test (test first of course).  As an aside the nice thing about the gokong project (and the kong terraform provider) are tested using docker containers so they are actually against the real Kong (no mocks anywhere to be seen!!!).

I quickly realised that I could write my own deserialise JSON method by simply implementing the UnmarshalJSON interface.  So my first stab at this was:


func (a *Api) UnmarshalJSON(data []byte) error {
fixedJson := strings.Replace(string(data), `"hosts":{}`, `"hosts":[]`, -1)
fixedJson = strings.Replace(fixedJson, `"uris":{}`, `"uris":[]`, -1)
fixedJson = strings.Replace(fixedJson, `"methods":{}`, `"methods":[]`, -1)
return json.Unmarshal([]byte(fixedJson), &a)
}

The idea behind this code was to simply replace the erroneous {} with [] in the JSON and then call the normal json.Unmarshal func.  Unfortunately this code goes into an infinite loop as you keep calling yourself.  Which was a shame as this code was nice and concise and would’ve done everything I wanted as it delegated the hard work of actually doing the deserialisation over to the normal json.Unmarshal func.

Then I found out that I can do what I want to do if I use a type alias.  Here is the corrected code:


func (a *Api) UnmarshalJSON(data []byte) error {
fixedJson := strings.Replace(string(data), `"hosts":{}`, `"hosts":[]`, -1)
fixedJson = strings.Replace(fixedJson, `"uris":{}`, `"uris":[]`, -1)
fixedJson = strings.Replace(fixedJson, `"methods":{}`, `"methods":[]`, -1)
type Alias Api
aux := &struct {
*Alias
}{
Alias: (*Alias)(a),
}
return json.Unmarshal([]byte(fixedJson), &aux)
}

The trick is to use a type alias to alias my Api type over to Alias (note this is a private declaration so is only scoped to the method).  Then I use an anonymous struct that uses an embedded type to inherit all of the fields from Api.  I then call json.Unmarshal on my anonymous struct with the fixed JSON.  Because my type Alias has a single embedded type which is Api it will serialise all of the Api fields to JSON using the fixed JSON.

I think this is a pretty neat solution as it means that anywhere in my whole library that deserialises the Api type will automatically use this code just by virtue of the fact that it implements the UnmarshalJSON interface.

This is a handy thing to know for your toolbox when you need to get involved in the JSON deserialisation pipeline in go.  Happy coding!

Leave a Comment