Safe JSON with Haskell
Haskell Generics + Aeson + QuickCheck For The Win
Recently I’ve been working on an HTTP API written in Haskell. One of the things that tends to be much easier and nicer in dynamic languages is dealing with JSON in these APIs, statically typed languages tend to have lots of boilerplate. Not only is this boilerplate annoying to write but it can cause small changes to the API into a big task.
With Haskell (actually GHC) this is not the case.
A great GHC extension is DeriveGeneric which can be used to define how to generate type class instances. Aeson has made great use of this, lets look at the following example:
1 |
|
Here we’ve defined a new record type Person
with 2 fields. You can imagine how this would look in JSON:
1 | { "name": "David" |
The instance ToJSON Person
automatically creates an instance that will allow the encode
function to generate exactly the JSON above. In a similar way, instance FromJSON Person
will allow us to decode
the above JSON into a Person record. That’s 2 lines of code!
Sometimes we might want to modify the JSON produced slightly, for example in order to generate lenses with TemplateHaskell you add an _
in front of the field names:
1 |
|
That’s not too bad, however now our JSON is a little less trustworthy, especially if we start doing even more customisation of the ToJSON
and FromJSON
instances. How do we know that the encode/decode functions work correctly?
This is where QuickCheck comes in. Using generic-arbitrary we can create an Arbitrary
instance of Person
and use QuickCheck to test random samples of Person
data:
1 | module Main where |
Now we can be pretty confident about the correctness of our JSON conversions.
Until I started using Haskell professionally, I wasn’t totally sold on static typing, however it’s examples like these that have converted me. With as little as 7 lines of code we can be sure that the data we are sending over the wire adheres to our API contract at compile time.