module Composite.Aeson.Record
( ToField(..), FromField(..), JsonField(..), field, field', optionalField, optionalField'
, JsonFormatRecord, DefaultJsonFormatRecord, defaultJsonFormatRecord
, RecordToJsonObject, recordToJsonObject, recordToJson
, RecordFromJson, recordFromJson
, recordJsonFormat
) where
import Composite.Aeson.Base
( JsonProfunctor(JsonProfunctor)
, JsonFormat(JsonFormat)
, wrappedJsonFormat
)
import Composite.Aeson.Formats.Default (DefaultJsonFormat(defaultJsonFormat))
import Composite.Record ((:->))
import Control.Lens (Wrapped(type Unwrapped, _Wrapped'), from, view)
import Control.Monad (join)
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.BetterErrors as ABE
import Data.Functor.Contravariant (Contravariant, contramap)
import Data.Functor.Identity (Identity(Identity))
import qualified Data.HashMap.Strict as HM
import Data.Proxy (Proxy(Proxy))
import Data.Text (Text, pack)
import Data.Vinyl (Rec((:&), RNil), rmap)
import GHC.TypeLits (KnownSymbol, symbolVal)
newtype ToField a = ToField { unToField :: a -> Maybe Aeson.Value }
instance Contravariant ToField where
contramap f (ToField g) = ToField (g . f)
newtype FromField e a = FromField { unFromField :: Text -> ABE.Parse e a }
instance Functor (FromField e) where
fmap f (FromField g) = FromField (fmap f . g)
data JsonField e a = JsonField (a -> Maybe Aeson.Value) (Text -> ABE.Parse e a)
field :: (Wrapped a', Unwrapped a' ~ a) => JsonFormat e a -> JsonField e a'
field fmt = field' (wrappedJsonFormat fmt)
field' :: JsonFormat e a -> JsonField e a
field' (JsonFormat (JsonProfunctor o i)) = JsonField (Just . o) (`ABE.key` i)
optionalField :: (Wrapped a', Unwrapped a' ~ Maybe a) => JsonFormat e a -> JsonField e a'
optionalField (JsonFormat (JsonProfunctor o i)) =
JsonField
(fmap o . view _Wrapped')
(\ k -> view (from _Wrapped') . join <$> ABE.keyMay k (ABE.perhaps i))
optionalField' :: JsonFormat e a -> JsonField e (Maybe a)
optionalField' (JsonFormat (JsonProfunctor o i)) =
JsonField
(fmap o)
(\ k -> join <$> ABE.keyMay k (ABE.perhaps i))
type JsonFormatRecord e rs = Rec (JsonField e) rs
class RecordToJsonObject rs where
recordToJsonObject :: Rec ToField rs -> Rec Identity rs -> Aeson.Object
instance RecordToJsonObject '[] where
recordToJsonObject _ = const mempty
instance forall s a rs. (KnownSymbol s, RecordToJsonObject rs) => RecordToJsonObject (s :-> a ': rs) where
recordToJsonObject (ToField aToField :& fs) (Identity a :& as) =
maybe id (HM.insert (pack . symbolVal $ (Proxy :: Proxy s))) (aToField a) $
recordToJsonObject fs as
recordToJson :: RecordToJsonObject rs => Rec ToField rs -> Rec Identity rs -> Aeson.Value
recordToJson = fmap Aeson.Object . recordToJsonObject
class RecordFromJson rs where
recordFromJson :: Rec (FromField e) rs -> ABE.Parse e (Rec Identity rs)
instance RecordFromJson '[] where
recordFromJson _ = pure RNil
instance forall s a rs. (KnownSymbol s, RecordFromJson rs) => RecordFromJson (s :-> a ': rs) where
recordFromJson (FromField aFromField :& fs) =
(:&)
<$> (Identity <$> aFromField (pack . symbolVal $ (Proxy :: Proxy s)))
<*> recordFromJson fs
recordJsonFormat :: (RecordToJsonObject rs, RecordFromJson rs) => JsonFormatRecord e rs -> JsonFormat e (Rec Identity rs)
recordJsonFormat formatRec =
JsonFormat $ JsonProfunctor
(recordToJson . rmap (\ (JsonField o _) -> ToField o ) $ formatRec)
(recordFromJson . rmap (\ (JsonField _ i) -> FromField i) $ formatRec)
class DefaultJsonFormatRecord rs where
defaultJsonFormatRecord :: JsonFormatRecord e rs
instance (KnownSymbol s, DefaultJsonFormat a, DefaultJsonFormatRecord rs) => DefaultJsonFormatRecord (s :-> a ': rs) where
defaultJsonFormatRecord = field defaultJsonFormat :& defaultJsonFormatRecord
instance DefaultJsonFormatRecord '[] where
defaultJsonFormatRecord = RNil