Home

Awesome

Build Status

Waargonaut

Flexible, precise, and efficient JSON decoding/encoding library. This package provides a plethora of tools for decoding, encoding, and manipulating JSON data.

Features

Example

data Image = Image
  { _imageWidth    :: Int
  , _imageHeight   :: Int
  , _imageTitle    :: Text
  , _imageAnimated :: Bool
  , _imageIDs      :: [Int]
  }
encodeImage :: Applicative f => Encoder f Image
encodeImage = E.mapLikeObj $ \img ->
    E.intAt "Width" (_imageWidth img)
  . E.intAt "Height" (_imageHeight img)
  . E.textAt "Title" (_imageTitle img)
  . E.boolAt "Animated" (_imageAnimated img)
  . E.listAt E.int "IDs" (_imageIDs img)
imageDecoder :: Monad f => D.Decoder f Image
imageDecoder = D.withCursor $ \curs -> do
  -- Move down into the JSON object.
  io <- D.down curs
  -- We need individual values off of our object,
  Image
    <$> D.fromKey "Width" D.int io
    <*> D.fromKey "Height" D.int io
    <*> D.fromKey "Title" D.text io
    <*> D.fromKey "Animated" D.bool io
    <*> D.fromKey "IDs" (D.list D.int) io

Zippers

Waargonaut uses zippers for its decoding which allows for precise control in how you interrogate your JSON input. Take JSON structures and decode them precisely as you require:

Input:
["a","fred",1,2,3,4]
Data Structure:
data Foo = Foo (Char,String,[Int])
Decoder:

The zipper starts the very root of the JSON input, we tell it to move 'down' into the first element.

fooDecoder :: Monad f => Decoder f Foo
fooDecoder = D.withCursor $ \cursor -> do
  fstElem <- D.down cursor

From the first element we can then decode the focus of the zipper using a specific decoder:

  aChar <- D.focus D.unboundedChar fstElem

The next thing we want to decode is the second element of the array, so we move right one step or tooth, and then attempt to decode a string at the focus.

  aString <- D.moveRight1 fstElem >>= D.focus D.string

Finally we want to take everything else in the list and combine them into a single list of Int values. Starting from the first element, we move right two positions (over the char and the string elements), then we use one of the provided decoder functions that will repeatedly move in a direction and combine all of the elements it can until it can no longer move.

  aIntList <- D.moveRightN 2 fstElem >>= D.rightwardSnoc [] D.int

Lastly, we build the Foo using the decoded values.

  pure $ Foo (aChar, aString, aIntList)

The zipper stores the history of your movements, so any errors provide information about the path they took prior to encountering an error. Making debugging precise and straight-forward.

Property Driven Development

This library is built to parse and produce JSON in accordance with the RFC 8259 standard. The data structures, parser, and printer are built to satify the Round Trip Property:

Which may be expressed using the following pseudocode:

parse . print = id

This indicates that any JSON produced by this library will be parsed back in as the exact data structure that produced it. This includes whitespace such as carriage returns and trailing whitespace. There is no loss of information.

There is also this property, again in pseudocode:

print . parse . print = print

This states that the printed form of the JSON will not change will be identical after parsing and then re-printing. There is no loss of information.

This provides a solid foundation to build upon.

NB: The actual code will of course return values that account for the possibility of failure. Computers being what they are.

TODO(s)

In no particular order...