calamity-0.3.0.0: Calamity/Internal/AesonThings.hs
module Calamity.Internal.AesonThings (
WithSpecialCases (..),
IfNoneThen,
ExtractFieldFrom,
ExtractFieldInto,
ExtractFields,
ExtractArrayField,
DefaultToEmptyArray,
DefaultToZero,
DefaultToFalse,
DefaultToTrue,
CalamityJSON (..),
CalamityJSONKeepNothing (..),
jsonOptions,
jsonOptionsKeepNothing,
) where
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Aeson.Types (Parser)
import Data.Kind
import Data.Reflection (Reifies (..))
import Data.String (IsString (fromString))
import Data.Typeable
import Control.Monad ((>=>))
import GHC.Generics
import GHC.TypeLits (KnownSymbol, symbolVal)
textSymbolVal :: forall n s. (KnownSymbol n, IsString s) => s
textSymbolVal = fromString $ symbolVal @n Proxy
data IfNoneThen label def
data ExtractFieldInto label field target
type ExtractFieldFrom label field = ExtractFieldInto label field label
data ExtractFields label fields
data ExtractArrayField label field
data MapFieldWith field ty
class PerformAction action where
runAction :: Proxy action -> Object -> Parser Object
instance (Reifies d Value, KnownSymbol label) => PerformAction (IfNoneThen label d) where
runAction _ o = do
v <- o .:? textSymbolVal @label .!= reflect @d Proxy
pure $ o & at (textSymbolVal @label) ?~ v
instance (KnownSymbol label, KnownSymbol field, KnownSymbol target) => PerformAction (ExtractFieldInto label field target) where
runAction _ o =
let v :: Maybe Value = o ^? ix (textSymbolVal @label) . _Object . ix (textSymbolVal @field)
in pure $ o & at (textSymbolVal @target) .~ v
instance PerformAction (ExtractFields label '[]) where
runAction _ = pure
instance
( KnownSymbol field
, PerformAction (ExtractFieldInto label field field)
, PerformAction (ExtractFields label fields)
) =>
PerformAction (ExtractFields label (field : fields))
where
runAction _ = runAction (Proxy @(ExtractFieldInto label field field)) >=> runAction (Proxy @(ExtractFields label fields))
instance (KnownSymbol label, KnownSymbol field) => PerformAction (ExtractArrayField label field) where
runAction _ o = do
a :: Maybe Array <- o .:? textSymbolVal @label
case a of
Just a' -> do
a'' <- Array <$> traverse (withObject "extracting field" (.: textSymbolVal @field)) a'
pure $ o & at (textSymbolVal @label) ?~ a''
Nothing -> pure o
instance (KnownSymbol field, Reifies ty (Value -> Value)) => PerformAction (MapFieldWith field ty) where
runAction _ o = pure (o & ix (textSymbolVal @field) %~ reflect @ty Proxy)
newtype WithSpecialCases (rules :: [Type]) a = WithSpecialCases a
class RunSpecialCase a where
runSpecialCases :: Proxy a -> Object -> Parser Object
instance RunSpecialCase '[] where
runSpecialCases _ = pure
instance (RunSpecialCase xs, PerformAction action) => RunSpecialCase (action : xs) where
runSpecialCases _ o = do
o' <- runSpecialCases (Proxy @xs) o
runAction (Proxy @action) o'
instance
(RunSpecialCase rules, Typeable a, Generic a, GFromJSON Zero (Rep a)) =>
FromJSON (WithSpecialCases rules a)
where
parseJSON = withObject (show . typeRep $ Proxy @a) $ \o -> do
o' <- runSpecialCases (Proxy @rules) o
WithSpecialCases <$> genericParseJSON jsonOptions (Object o')
data DefaultToEmptyArray
instance Reifies DefaultToEmptyArray Value where
reflect _ = Array mempty
data DefaultToZero
instance Reifies DefaultToZero Value where
reflect _ = Number 0
data DefaultToFalse
instance Reifies DefaultToFalse Value where
reflect _ = Bool False
data DefaultToTrue
instance Reifies DefaultToTrue Value where
reflect _ = Bool True
newtype CalamityJSON a = CalamityJSON
{ unCalamityJSON :: a
}
instance (Typeable a, Generic a, GToJSON Zero (Rep a), GToEncoding Zero (Rep a)) => ToJSON (CalamityJSON a) where
toJSON = genericToJSON jsonOptions . unCalamityJSON
toEncoding = genericToEncoding jsonOptions . unCalamityJSON
instance (Typeable a, Generic a, GFromJSON Zero (Rep a)) => FromJSON (CalamityJSON a) where
parseJSON = fmap CalamityJSON . genericParseJSON jsonOptions
-- | version that keeps Nothing fields
newtype CalamityJSONKeepNothing a = CalamityJSONKeepNothing
{ unCalamityJSONKeepNothing :: a
}
instance (Typeable a, Generic a, GToJSON Zero (Rep a), GToEncoding Zero (Rep a)) => ToJSON (CalamityJSONKeepNothing a) where
toJSON = genericToJSON jsonOptionsKeepNothing . unCalamityJSONKeepNothing
toEncoding = genericToEncoding jsonOptionsKeepNothing . unCalamityJSONKeepNothing
instance (Typeable a, Generic a, GFromJSON Zero (Rep a)) => FromJSON (CalamityJSONKeepNothing a) where
parseJSON = fmap CalamityJSONKeepNothing . genericParseJSON jsonOptionsKeepNothing
jsonOptions :: Options
jsonOptions =
defaultOptions
{ sumEncoding = UntaggedValue
, fieldLabelModifier = camelTo2 '_' . filter (/= '_')
, omitNothingFields = True
}
jsonOptionsKeepNothing :: Options
jsonOptionsKeepNothing =
defaultOptions
{ sumEncoding = UntaggedValue
, fieldLabelModifier = camelTo2 '_' . filter (/= '_')
, omitNothingFields = False
}