Typing Haskell: Working with JSON

In my previous post I wrote about playing with Haskell to develop an application. Through this experience I have looked how to encode and decode JSON objects. JSON (JavaScript Object Notation) is the de-facto data-interchange format for RESTful API requests and responses.

As an example I have developed a RESTful service that provides a source list and a destination list of countries for money transfer.

This post is Literate Haskell. First we need to define the module WorkingWithJSON:


> {-# LANGUAGE DeriveGeneric #-}
> module WorkingWithJSON where
>
> import Data.Aeson
> import GHC.Generics

The first line is a language pragma to enable us to derive instances of the type classes Generic and Generic1, defined in the moduleGHC.Generics. This module is imported. The combination of the language pragma and the import of GHC.Generics allows us to employ Generic Programming. Generic Programming is a convenient way to apply the JSON encoding and decoding functionalities from the module Data.Aeson. This module is provided by the package aeson.

I model a country by its ISO 3166–1 alpha-2 and its ISO 3166 country name. In Haskell, this can be specified by the Country Algebraic Data Type :


> data Country = Country
> { isoCode :: String
> , name :: String
> } deriving (Generic, Show)

This Country type derives Generic. Since theGeneric type class does not come from the standalone GHC, the extension (language pragma) DeriveGeneric is required. The type class Show allows data values to be converted into Haskell string literals. This type class comes from the standalone and therefore does not require any extension to derive its representation.

The import Data.Aeson provides two type classes ToJSON and FromJSON to encode to and decode from JSON objects respectively. Below is a snippet of the definition of ToJSON:

class ToJSON a where
toJSON :: a -> Value
default toJSON :: (Generic a, GToJSON Zero (Rep a)) => a -> Value
toJSON = genericToJSON defaultOptions
...

The type class ToJSON does not require its instances to implement any of its functions as default implementations have already been provided. The restriction on the type of the default implementation of toJSON requires the type variable a to implement type Generic. This is the reason why the language pragma DeriveGeneric and the import GHC.Generics are required. This default implementation uses the generic type structure to encode data type value into JSON objects. The type class FromJSON is defined similarly.

As a result, to use these two type classes, it is sufficient to declare the Country as their instances as follows:


> instance ToJSON Country
> instance FromJSON Country

To test this, copy and paste this post into a file named WorkingWithJSON.lhs (.lhs is the extension for Literate Haskell), and run the following commands (The option -XOverloadedStrings allows Haskell string literals to be polymorphic):

ghci -XOverloadedStrings WorkingWithJSON.lhs
*WorkingWithJSON> decode "{\"name\":\"United Kingdom\",\"isoCode\":\"GB\"}" :: Maybe Country 
Just (Country {isoCode = "GB", name = "United Kingdom"})
*WorkingWithJSON> encode (Country "GB" "United Kingdom")"{\"name\":\"United Kingdom\",\"isoCode\":\"GB\"}"