Encoding and decoding JSON with Aeson
Tags: functional, haskell
Published: 2019-05-15 21:09:13

---
title: Encoding and decoding JSON with Aeson
published: true
description: #100DaysOfFP Day 17: Using the Aeson library to safely decode and decode JSON in Haskell
series: 100 Days Of FP
cover_image: https://images.pexels.com/photos/209137/pexels-photo-209137.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260
tags: fp,haskell
---

In the past days I've been working on a small app to parse the transactions statements I get from my bank with the idea to generate something that is easier to visualize.

I get a `.txt` file where each line corresponds to one transaction and from that I wanted to generate a JSON file that then I can use in a charting app.

In the process I learned a few things about working with JSON in Haskell using [Aeson](http://hackage.haskell.org/package/aeson-1.4.3.0/docs/Data-Aeson.html).

To decode and encode values from and to JSON we use the `decode` and `encode` methods (good naming there :wink:).

```haskell
decode :: FromJSON a => ByteString -> Maybe a
encode :: ToJSON a => a -> ByteString
```
_NOTE_: By setting the [OverloadedStrings](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#overloaded-strings) compiler extension we can treat string literals as not only `String` but also `ByteString`, `Text`, and others.


We can learn a lot about how Aeson works from these type signatures.

`decode` takes a `ByteString` (we can think of it as a string) and returns a `Maybe a` with the constraint that this `a` has to have the `FromJSON` type class. With that name we can assume that it's for types that can be parsed from JSON.

`encode` on the other hand takes a value of type `a`, this time with the `ToJSON` type class and returns a string. In this case we can assume `ToJSON` means a type that can be converted to JSON.

Let's then try to use them. Notice that we need to specify what type we are decoding/encoding so the compiler know what instance of FromJSON/TOJSON should use.

```
λ> :set -XOverloadedStrings
λ> import Data.Aeson
λ> decode "true" :: Maybe Bool
Just True
λ> decode "tru" :: Maybe Bool
Nothing
λ> decode "123" :: Maybe Bool
Nothing
λ> decode "123" :: Maybe Int
Just 123
λ> encode (123 :: Int)
"123"
λ> encode ("hola" :: String)
"\"hola\""
λ> encode (Just 123 :: Maybe Int)
"123"
λ> encode (Nothing :: Maybe Int)
"null"
```

Decoding returns a `Maybe a` because the operation can fail since we don't know if the JSON in the string is properly formatted. Whereas encoding, since we have the `ToJSON` constraint should always succeed.

All of those types "work out of the box" because the instances are already implemented by Aeson :tada:

```
λ> :i Int
data Int = ghc-prim-0.5.3:GHC.Types.I# ghc-prim-0.5.3:GHC.Prim.Int#
        -- Defined in ‘ghc-prim-0.5.3:GHC.Types’
...
instance ToJSON Int
  -- Defined in ‘aeson-1.4.2.0:Data.Aeson.Types.ToJSON’
instance FromJSON Int
  -- Defined in ‘aeson-1.4.2.0:Data.Aeson.Types.FromJSON’
```

But we are not going to work only with primitive types, are we?

But if we try to encode a value of a custom type we get an error.

```
λ> data Obj = Obj { id :: Int }
λ> encode Obj { id = 1 }

<interactive>:5:1: error:
    • No instance for (ToJSON Obj) arising from a use of ‘encode’
    • In the expression: encode Obj {id = 1}
      In an equation for ‘it’: it = encode Obj {id = 1}
```

We need to implement `ToJSON` for this type.

```
λ> :i ToJSON
class ToJSON a where
  toJSON :: a -> Value
  default toJSON :: (GHC.Generics.Generic a,
                     aeson-1.4.2.0:Data.Aeson.Types.ToJSON.GToJSON
                       Value Zero (GHC.Generics.Rep a)) =>
                    a -> Value
  toEncoding :: a -> Encoding
  toJSONList :: [a] -> Value
  toEncodingList :: [a] -> Encoding
        -- Defined in ‘aeson-1.4.2.0:Data.Aeson.Types.ToJSON’
```

We could do it manually:

```haskell
{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson

newtype Obj = Obj
  { a :: Int
  }

instance ToJSON Obj where
  toJSON Obj {a = a} = object [("a", toJSON a)]

-- object :: [Pair] -> Value
-- type Pair = (Text, Value) -- Value here is the Aeson.Value
```

Or we can use some compiler magic to do it for us using the compiler extensions: [DeriveAnyClass](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html?highlight=deriveanyclass#extension-DeriveAnyClass) & [DeriveGeneric](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html?highlight=deriveanyclass#extension-DeriveGeneric).

```haskell
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric  #-}
{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson

newtype Obj = Obj
  { a :: Int
  } deriving (Generic, ToJSON)
``` 

_Et voilà_ :tada:

That is what we'll be doing most of the time, unless we to encode or decode our data in some particular way.

Implementing and deriving `FromJSON` works pretty much the same so it's not worth to show it.

## Conclusion

The compiler magic of deriving type classes instances makes it really easy to work with JSON in Haskell. We still have the safety of our types without having to implement custom encoder/decoders for everything like other languages (e.g. Elm).