Golang’s built-in JSON library focuses on the programmer. When it encounters an error while decoding a JSON file, it produces a message about the Go type it couldn’t deserialize. That’s perfect for a developer writing code, but it’s not so good for an end-user.

Users only ever touch text, so their error messages should be about text. They shouldn’t need to understand the inner workings of a program in order to figure out what’s wrong with their text file. Luckily, it only takes a few small changes to get them what they need.

Annotating errors with their “path”

The JSON decoder works by recursively exploring every field in the input. When it encounters something it doesn’t understand, it builds an error message and returns it through the call stack back to the user.

Because the error message is generated when the decoder is parsing a specific field, it only contains information about that field. In a complex JSON document, that might not be enough to pinpoint where the decoder failed. What we really need is a list of keys, a “path”, from the root JSON object down to the field that caused the problem.

The solution works like this: When the decoder bubbles up an error message through the call stack, it’s essentially retracing its steps. Along the way, it can add each key to the error message’s path. By the time the error message is handed off to the user, it contains the entire path.

Instead of just “couldn’t unmarshal into Go slice”, the user now sees that their problem is in “$.pod.containers.1.env”.

Catching typos

Another fun thing to do with JSON parsing is catching typos. Golang’s encoding/json just ignores fields it doesn’t recognize. This means you can mistype an optional field in your Kubernetes manifest and you won’t find out until its absence breaks something.

This feature is best implemented inside the JSON decoder, but what if you depend on a library (e.g. the Kubernetes API decoder) that uses encoding/json? We can’t rewrite all our dependencies to use a custom JSON library.

An imperfect but completely functional solution is to re-encode a freshly decoded object and see if the output matches the input. There are some implementation details around “empty vs. omitted”, but the end result is this:

If the original JSON contains a field that isn’t in the re-encoded JSON, that field is a typo.

It works great, and we actually found a bunch of minor errors in our own Kubernetes manifests this way.

Try it!

Check out the source code and see it in action with Koki Short.