{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Aeson.Filthy
(
JSONString(..)
, (.:$)
, (.=$)
, OneOrZero(..)
, YesOrNo(..)
, OnOrOff(..)
, AnyBool(..)
, EmptyAsNothing(..)
, RFC2822Time(..)
, (.:~)
) where
import Control.Applicative (Alternative (..))
import Control.Monad (MonadPlus)
import Control.Monad.Fix (MonadFix)
import Data.Aeson
import Data.Aeson.Types (Pair, Parser)
import Data.Bits (Bits, FiniteBits)
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Lazy as HM
import Data.Ix (Ix)
import Data.Monoid
import Data.Semigroup (Semigroup)
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Time
import Foreign.Storable (Storable)
import GHC.Generics (Generic, Generic1)
newtype JSONString a = JSONString { jsonString :: a }
deriving (Bounded, Enum, Eq, Ord, Read, Show, Ix, Generic, FiniteBits, Bits, Storable, Num, Integral, Real, Floating, Fractional, RealFrac, RealFloat, IsString)
instance ToJSON a => ToJSON (JSONString a) where
toJSON = String . T.decodeUtf8 . BL.toStrict . encode . jsonString
instance FromJSON a => FromJSON (JSONString a) where
parseJSON = withText "a double-encoded json value (JSONString)"
(maybe (error "couldn't decode string") return . evil)
where evil = fmap JSONString . decodeStrict . T.encodeUtf8
(.:$) :: FromJSON a => Object -> Text -> Parser a
o .:$ t = jsonString <$> o .: t
(.=$) :: ToJSON a => Text -> a -> Pair
n .=$ o = n .= JSONString o
newtype YesOrNo = YesOrNo { yesOrNo :: Bool }
deriving (Bounded, Enum, Eq, Ord, Read, Show, Ix, Generic, FiniteBits, Bits, Storable)
newtype OnOrOff = OnOrOff { onOrOff :: Bool }
deriving (Bounded, Enum, Eq, Ord, Read, Show, Ix, Generic, FiniteBits, Bits, Storable)
newtype OneOrZero = OneOrZero { oneOrZero :: Bool }
deriving (Bounded, Enum, Eq, Ord, Read, Show, Ix, Generic, FiniteBits, Bits, Storable)
newtype AnyBool = AnyBool { anyBool :: Bool }
deriving (Bounded, Enum, Eq, Ord, Read, Show, Ix, Generic, FiniteBits, Bits, Storable)
instance ToJSON YesOrNo where
toJSON (YesOrNo True) = "yes"
toJSON _ = "no"
instance FromJSON YesOrNo where
parseJSON "yes" = pure $ YesOrNo True
parseJSON _ = pure $ YesOrNo False
instance ToJSON OnOrOff where
toJSON (OnOrOff True) = "on"
toJSON _ = "off"
instance FromJSON OnOrOff where
parseJSON "on" = pure $ OnOrOff True
parseJSON _ = pure $ OnOrOff False
instance ToJSON OneOrZero where
toJSON (OneOrZero True) = Number 1
toJSON _ = Number 0
instance FromJSON OneOrZero where
parseJSON (Number 1) = pure $ OneOrZero True
parseJSON _ = pure $ OneOrZero False
instance FromJSON AnyBool where
parseJSON (Number 1) = pure $ AnyBool True
parseJSON (String "1") = pure $ AnyBool True
parseJSON (String "true") = pure $ AnyBool True
parseJSON (String "yes") = pure $ AnyBool True
parseJSON (String "on") = pure $ AnyBool True
parseJSON (Bool b) = pure $ AnyBool b
parseJSON _ = pure $ AnyBool False
newtype EmptyAsNothing a = EmptyAsNothing { emptyAsNothing :: Maybe a}
deriving (Eq, Ord, Read, Show, Functor, Applicative, Alternative, Monad, MonadPlus, Foldable, Semigroup, Monoid, MonadFix, Generic, Generic1)
instance Traversable EmptyAsNothing where
traverse f = fmap EmptyAsNothing . traverse f . emptyAsNothing
instance ToJSON a => ToJSON (EmptyAsNothing a) where
toJSON = maybe "" toJSON . emptyAsNothing
instance FromJSON a => FromJSON (EmptyAsNothing a) where
parseJSON "" = pure $ EmptyAsNothing Nothing
parseJSON x = EmptyAsNothing <$> parseJSON x
newtype RFC2822Time
= RFC2822Time { fromRFC2822Time :: UTCTime }
deriving (Show, Eq, Ord, FormatTime)
instance FromJSON RFC2822Time where
parseJSON = withText "RFC2822Time" $ \t ->
let ts = T.unpack . T.replace " " "" . T.replace "\n" "" $ t
in case getFirst . foldMap (\f -> parseTimeM True defaultTimeLocale f ts) $ (
do
dow <- ["", "%a,"]
date <- ["%e%b%Y", "%e%b%y"]
time <- ["%H:%M:%S", "%H:%M"]
zone <- ["%Z"]
pure (mconcat [dow, date, time, zone])) of
Just d -> pure (RFC2822Time d)
_ -> fail "could not parse RFC2822 format time"
{-# INLINE parseJSON #-}
instance ToJSON RFC2822Time where
toJSON = toJSON . rfc2822Time
toEncoding = toEncoding . rfc2822Time
rfc2822Format :: String
rfc2822Format = "%a, %e %b %Y %T %z"
rfc2822Time :: RFC2822Time -> String
rfc2822Time = formatTime defaultTimeLocale rfc2822Format
(.:~) :: FromJSON a => Object -> Text -> Parser a
o .:~ key = o .: key <|> maybe empty parseJSON go
where go = lookup (T.toLower key) [(T.toLower k, v) | (k,v) <- HM.toList o]