0

I have JSON date data in the following form:

  {"date": "2015-04-12"}

and a corresponding haskell type:

data Date = Date {
      year   :: Int
    , month  :: Int
    , day    :: Int
    }

How can I write the custom FromJSON and ToJSON functions for the Aeson library? Deriving the instances does not work because of the formatting.

jules
  • 1,897
  • 2
  • 16
  • 19

2 Answers2

8

Why reinvent the wheel? There is a semi-standard representation for what you call Date in the time package - it is called Day. It gets better: not only does that same package even give you the utilities for parsing Day from the format you have, those utilities are even exported to aeson. Yep, there are already ToJSON and FromJSON instances in aeson for Day:

ghci> :set -XOverloadedStrings
ghci> import Data.Time.Calendar
ghci> import Data.Aeson
ghci> fromJSON "2015-04-12" :: Result Day
Success 2015-04-12
ghci> toJSON (fromGregorian 2015 4 12)
String "2015-04-12"

If you really want to extract the days, months, and years, you can always use toGregorian :: Day -> (Integer, Int, Int). Sticking to the standard abstraction is probably a good long-term choice though.

Alec
  • 31,829
  • 7
  • 67
  • 114
1

You have convert y/m/d to/from string

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}

import Control.Monad
import Data.Aeson
import qualified Data.Text as T
import Text.Read (readMaybe)
-- import qualified Data.Attoparsec.Text as A

data Date = Date Int Int Int deriving (Read, Show)

instance ToJSON Date where
    toJSON (Date y m d) = toJSON $ object [
        "date" .= T.pack (str 4 y ++ "-" ++ str 2 m ++ "-" ++ str 2 d)]
        where
            str n = pad . show where
                pad s = replicate (n - length s) '0' ++ s

instance FromJSON Date where
    parseJSON = withObject "date" $ \v -> do
        str <- v .: "date"
        let
            ps@(~[y, m, d]) = T.split (== '-') str
        guard (length ps == 3)
        Date <$> readNum y <*> readNum m <*> readNum d
        where
            readNum = maybe (fail "not num") return . readMaybe . T.unpack

    -- -- or with attoparsec
    -- parseJSON = withObject "date" $ \v -> do
    --  str <- v .: "date"
    --  [y, m, d] <- either fail return $
    --      A.parseOnly (A.decimal `A.sepBy` A.char '-') str
    --  return $ Date y m d