Cogs and Levers A blog full of technical stuff

Basic Aeson Usage

Introduction

JSON is a common interchange data format used across the web these days. It’s so popular because it’s easy to work with, marries directly with Javascript (really helping out the web guys) and its structure allows you to specify complex information in a simple, readable format.

In today’s post I’m going to walk through some basic usage of the Haskell library aeson which provides some tools for working with JSON.

Getting started

First of all, you’ll need the aeson library installed locally to work with it. You can install this with cabal:

$ cabal install aeson

While that’s installing, take a look at the examples up in the repo for aeson.

Defining your data structure

The first example, Simple.hs starts with defining the data structure that we’re expecting to work with:

data Coord = Coord { x :: Double, y :: Double }
             deriving (Show)

This is pretty simple. Just a 2d co-ordinate. The example goes on to define instances of ToJSON and FromJSON to facilitate the serialization and deserialization of this data (respectively).

instance ToJSON Coord where
  toJSON (Coord xV yV) = object [ "x" .= xV,
                                  "y" .= yV ]

instance FromJSON Coord where
  parseJSON (Object v) = Coord <$>
                         v .: "x" <*>
                         v .: "y"
  parseJSON _ = empty

The only really curly bit about this, is the use of the (.=) and the (.:) operators. These will pair-up or extract data in context of your JSON object.

Simplify

With all of this said, now take a look at Generic.hs. This file makes use of the DeriveGeneric language extension to write the ToJSON and FromJSON implementations above. Those type class instances now read as follows:

instance FromJSON Coord
instance ToJSON Coord

The type of Coord needs to be augmented slightly to include the Generic type class.

data Coord = Coord { x :: Double, y :: Double }
             deriving (Show, Generic)

Pretty easy.

Reading and writing

Finally, we need to actually poke a string into this thing and pull built objects back out of it. The main covers this:

main :: IO ()
main = do
  let req = decode "{\"x\":3.0,\"y\":-1.0}" :: Maybe Coord
  print req
  let reply = Coord 123.4 20
  BL.putStrLn (encode reply)

You can see that we can turn a string into a Coord object with little effort now.

Extending this a little further to read values off of disk, we can lean on readFile from Data.ByteString.Lazy:

λ> x <- (eitherDecode <$> B.readFile "coord.json") :: IO (Either String Coord)
λ> x
Right (Coord {x = 3.5, y = -2.2})

eitherDecode was either going to give us an error message on the Left, or the built object on the Right.

That’s it for today.