{-# LANGUAGE PolyKinds #-} {-# LANGUAGE UndecidableInstances #-} module Data.Aeson.Deriving.Known where import Data.Aeson import qualified Data.HashMap.Strict as HashMap import Data.Kind (Type) import Data.Proxy import Data.Text (pack) import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) infix 3 := infix 6 ==> -- | Represents the null JSON 'Value'. data Null -- | Phantom data type to make explicit which fields we pass for Aeson options. Polykinded -- in the second argument so it can take i.e. Booleans or Symbols where needed. -- -- Also used for specifying constant values added to, or required from, an encoding. -- See "Data.Aeson.Deriving.WithConstantFields". data field := (value :: k) -- | Represents a function that maps the first value to the second, -- and otherwise does nothing but return the input. data a ==> b -- | Represents the function that turns nulls into the given default value. data WithDefault (val :: k) -- | Constant JSON values class KnownJSON (a :: k) where jsonVal :: Proxy a -> Value instance KnownSymbol str => KnownJSON (str :: Symbol) where jsonVal = String . pack . symbolVal instance KnownBool b => KnownJSON (b :: Bool) where jsonVal = Bool . boolVal instance KnownJSON Null where jsonVal Proxy = Null instance KnownJSONList (xs :: [k]) => KnownJSON xs where jsonVal Proxy = toJSON $ listVal (Proxy @xs) -- | Constant boolean values class KnownBool (b :: Bool) where boolVal :: Proxy b -> Bool instance KnownBool 'True where boolVal Proxy = True instance KnownBool 'False where boolVal Proxy = False -- | Constant JSON lists class KnownJSONList (xs :: [k]) where listVal :: Proxy xs -> [Value] instance KnownJSONList '[] where listVal Proxy = [] instance (KnownJSON x, KnownJSONList xs) => KnownJSONList (x ': xs) where listVal Proxy = jsonVal (Proxy @x) : listVal (Proxy @xs) -- | Constant JSON objects class KnownJSONObject (a :: k) where objectVal :: Proxy a -> Object instance KnownJSONObject '[] where objectVal Proxy = mempty instance (KnownJSONObject fields, KnownSymbol key, KnownJSON val) => KnownJSONObject ((key := val) ': fields) where objectVal Proxy = HashMap.insert (pack . symbolVal $ Proxy @key) (jsonVal $ Proxy @val) (objectVal $ Proxy @fields) -- | JSON ('Value') functions class KnownJSONFunction (a :: Type) where functionVal :: Proxy a -> Value -> Value -- instance All KnownJSON [a, b] => KnownJSONFunction (a ==> b) where instance (KnownJSON a, KnownJSON b) => KnownJSONFunction (a ==> b) where functionVal Proxy x | x == jsonVal (Proxy @a) = jsonVal (Proxy @b) | otherwise = x instance KnownJSON a => KnownJSONFunction (WithDefault a) where functionVal Proxy = \case Null -> jsonVal $ Proxy @a x -> x