JSON, Aeson and Template Haskell for fun and profit
At times, handling JSON in Haskell might seem difficult, but once you get the hang of it, you will in fact see it is not.
There are a few libraries that parse and encode JSON in Haskell, but recently one particular has gained a lot of popularity, and for a good reason. The library in question is Bryan O’Sullivan’s Aeson.
Since all this imposing knowledge came out of an effort to build a Meetup.com API client library in Haskell, we’ll be dealing with Meetup entities in the following examples. Consequently, we have an
Eventtype that was obtained by querying the
/2/events API endpoint (example response), and an
RSVPtype, which we get from the
/2/rsvpsAPI endpoint (example response).
The regular way to parse JSON with Aeson is to implement a
FromJSONinstance for the data type we want to build from JSON. It tells Aeson what JSON fields to pluck from and how to translate those fields to a Haskell construct. Inversely, if we wanted to encode a type to JSON, we’d have to implement a
Here’s an example of how to manually implement a
FromJSONinstance for an
If you are interested, you can load the file into the GHCi REPL with
$ ghci src/Types/Event.hsand see what the
As you can see, following the
Eventtype definition, there’s a definition of the
parseJSONfunction, and it looks like the
parseJSONdefinition could be categorised as a boilerplate code. Why? Because each time we add or remove a record field in the
Eventdata constructor, we also need to change the
There is more. If the example above contained a
ToJSONdefinition, we’d have to change it as well. Now, stretch your imagination for a second and imagine we had more than one type. This situation could get out of hand very quickly and even though the type system can inform us of it, it would still be annoying.
Making the GHC work for you
Feeling tired after all the hard work we had to do previously, it kind of makes us wonder if there’s an easier way to do this… Turns out there is, and it’s called Template Haskell (called TH by friends).
I won’t go into the whats and the hows of TH (mainly since I myself don’t completely understand it yet), but you can think of it as Lisp’s macro system but with types. Because everything is better with some types.
Long story short, using the
Data.Aeson.TH package we can make the GHC define the FromJSON and ToJSON instances at compile-time for us, and this is how:
As before, you can load up the code into GHCi the same as before and check the result by calling
We can see in the example above that, even though there is no
fromJSON definition, we’re still able to decode the
ByteString to JSON. We still need to import
Data.Aeson to have access to the
Not much convincing is needed to see that this approach is much better than the previous one. Once
RSVP types change, GHC changes the `FromJSON` and `ToJSON` definitions at our demand.
Reduce boilerplate wherever possible, since the tools to do it are already there. You’ll be happier and your general well-being will improve.
In some future post I’ll try to figure out Template Haskell and explain it in an approachable way.
If anyone can explain what “A continuation-based parser type” means, I’d be happy to hear it out.
Originally published at pltconfusion.com.