Написание пользовательских экземпляров для данных даты JSON в Aeson

У меня есть данные даты JSON в следующей форме:

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

и соответствующий тип haskell:

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

Как я могу написать кастом FromJSON а также ToJSON функции для библиотеки Aeson? Извлечение экземпляров не работает из-за форматирования.

2 ответа

Решение

Вы преобразовали Y / M / D в / из строки

{-# 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

Зачем изобретать велосипед? Существует полустандартное представление того, что вы называете Date в time пакет - это называется Day, Это становится лучше: тот же пакет не только дает вам утилиты для разбора Day из того формата, который у вас есть, эти утилиты даже экспортируются в aeson, Да, уже есть ToJSON а также FromJSON случаи в aeson за 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"

Если вы действительно хотите извлечь дни, месяцы и годы, вы всегда можете использовать toGregorian :: Day -> (Integer, Int, Int), Придерживаться стандартной абстракции, вероятно, является хорошим долгосрочным выбором.

Другие вопросы по тегам