{-# LANGUAGE LambdaCase, GADTs, FlexibleContexts, ScopedTypeVariables #-}
module Data.Aeson.JsonInfo where
import Control.Applicative
import Data.Aeson
import Data.Aeson.Types
import Data.Text (Text, unpack)
import qualified Data.HashMap.Strict as HM
import Data.Monoid ((<>))
import Data.Typeable (Typeable, typeRep)
import Data.Proxy (Proxy(..))
type JsonInfo a = JsonFieldInfo a a
-- | 'JsonFieldInfo'
--
-- Use the 'JsonInfo' type alias instead
--
-- > Data R = R { a :: Text, b :: Int32 } deriving Typeable
-- >
-- > rJsonInfo :: JsonInfo R
-- > rJsonInfo = R
-- > <$> "a" @: a
-- > <*> 'b @: b
-- >
-- > instance ToJSON R where
-- > toJSON = jiToJSON rJsonInfo
-- > instance FromJSON R where
-- > parseJSON = jiParseJSON rJsonInfo
data JsonFieldInfo t a where
Pure :: a -> JsonFieldInfo t a
Map :: (a -> b) -> JsonFieldInfo t a -> JsonFieldInfo t b
App :: JsonFieldInfo t (a -> b) -> JsonFieldInfo t a -> JsonFieldInfo t b
Field :: (ToJSON a, FromJSON a)
=> (t -> a) -- ^ the accessor
-> Text -- ^ name in the json object
-> Maybe a -- ^ default value
-> JsonFieldInfo t a
instance Functor (JsonFieldInfo t) where
fmap = Map
instance Applicative (JsonFieldInfo t) where
pure = Pure
(<*>) = App
fieldJInfo, (@:) :: (ToJSON a, FromJSON a) => Text -> (t -> a) -> JsonFieldInfo t a
fieldJInfo name accessor = Field accessor name Nothing
(@:) = fieldJInfo
fieldJInfoOpt, (@:?) :: (ToJSON (Maybe a), FromJSON (Maybe a)) => Text -> (t -> Maybe a) -> JsonFieldInfo t (Maybe a)
fieldJInfoOpt name accessor = Field accessor name (Just Nothing)
(@:?) = fieldJInfoOpt
jiToJSON :: JsonInfo t -> t -> Value
jiToJSON ji = Object . jiToObject ji
jiToObject :: forall t. JsonInfo t -> t -> Object
jiToObject ji t = mkObj ji HM.empty
where
mkObj :: JsonFieldInfo t a -> Object -> Object
mkObj = \case
Pure _ -> id
Map _ x -> mkObj x
App x y -> mkObj x . mkObj y
Field accessor name _ -> HM.insert name . toJSON $ accessor t
jiParseJSON :: Typeable t => JsonInfo t -> Value -> Parser t
jiParseJSON (ji :: JsonInfo t) =
withObject (show $ typeRep (Proxy :: Proxy t))
$ jiFromObject ji
jiFromObject :: forall t. JsonInfo t -> Object -> Parser t
jiFromObject ji o = go ji
where
go :: forall a. JsonFieldInfo t a -> Parser a
go = \case
Pure x -> pure x
Map f x -> f <$> go x
App x y -> go x <*> go y
Field _ name def -> case HM.lookup name o of
Just v -> parseJSON v
Nothing -> maybe (fail $ "key " <> unpack name <> " not present") return def