highjson-0.2.0.1: src/Data/Json/Serialiser.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
module Data.Json.Serialiser
( -- * Serialising to different types
serialiseJsonBs, serialiseJsonBsl, serialiseJsonT
-- * Description how to serialise JSON from a Haskell type
, ToJson(..)
-- * DSL to easily create serialiser for custom Haskell types
, runSerSpec, SerSpec(..), (.<-), KeyedSerialiser, SerObjSpec(..)
, SpecKey, (.:), (.:?)
-- * Low level JSON serialising helpers
, ObjectBuilder, emptyObject, Value, (.=), (.=#), row, array, nullValue
)
where
import Data.BufferBuilder.Json
import Data.Int
import Data.Monoid
import Data.Typeable
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
instance (ToJson a, ToJson b) => ToJson (Either a b) where
toJson x =
case x of
Left y -> toJson y
Right z -> toJson z
instance (ToJson a, ToJson b) => ToJson (a, b) where
toJson (x, y) = toJson [ toJson x, toJson y ]
instance ToJson Int64 where
toJson i =
let j :: Int
j = fromIntegral i
in toJson j
-- | A json key and a getter
data SpecKey k t
= SpecKey
{ k_key :: !T.Text
, k_getVal :: !(k -> Maybe t)
}
-- | Construct a 'SpecKey' mapping a json key to a getter function
(.:) :: (ToJson t, Typeable t) => T.Text -> (k -> t) -> SpecKey k t
k .: getter = SpecKey k (Just . getter)
{-# INLINE (.:) #-}
-- | Construct a 'SpecKey' mapping a json key to a getter function of
-- a 'Maybe' type. This allows to omit the key when generating json instead of
-- setting it to null.
(.:?) :: (ToJson t, Typeable t) => T.Text -> (k -> Maybe t) -> SpecKey k (Maybe t)
k .:? getter =
SpecKey k $ \obj ->
let val = getter obj
in case val of
Nothing -> Nothing
Just _ -> Just val
{-# INLINE (.:?) #-}
newtype KeyedSerialiser k
= KeyedSerialiser { unKeyedSerialiser :: Value }
-- | Associate a JSON key with a serialiser
(.<-) :: ToJson a => T.Text -> a -> KeyedSerialiser k
a .<- b = KeyedSerialiser $ toJson (a .= b)
{-# INLINE (.<-) #-}
-- | Parser specification. Use 'OnlyConstr' for normal types and 'FirstConstr'/'NextConstr' for sum types
data SerSpec k where
SingleConstr :: SerObjSpec k ts -> SerSpec k
MultiConstr :: (k -> KeyedSerialiser k) -> SerSpec k
runSerSpec :: SerSpec k -> k -> Value
runSerSpec spec input =
case spec of
SingleConstr fullSpec -> runSerObjSpec fullSpec input
MultiConstr getVal -> unKeyedSerialiser $ getVal input
{-# INLINE runSerSpec #-}
-- | List of 'SpecKey's defining the serialisation of values to json
data SerObjSpec k (ts :: [*]) where
SerObjSpecNil :: SerObjSpec k '[]
(:&&&:) :: (ToJson t, Typeable t) => !(SpecKey k t) -> !(SerObjSpec k ts) -> SerObjSpec k (t ': ts)
infixr 5 :&&&:
-- | Convert a 'SerObjSpec' into an 'Value' for defining 'ToJson' instances
runSerObjSpec :: SerObjSpec k ts -> k -> Value
runSerObjSpec spec input = toJson (buildSpec spec input)
{-# INLINE runSerObjSpec #-}
buildSpec :: SerObjSpec k ts -> k -> ObjectBuilder
buildSpec spec input =
case spec of
SerObjSpecNil -> mempty
(SpecKey key getVal :&&&: xs) ->
case getVal input of
Nothing -> buildSpec xs input
Just val -> key .= getVal input <> buildSpec xs input
-- | Serialise json to a strict 'BS.ByteString'
serialiseJsonBs :: ToJson a => a -> BS.ByteString
serialiseJsonBs = encodeJson
{-# INLINE serialiseJsonBs #-}
-- | Serialise json to a lazy 'BSL.ByteString'
serialiseJsonBsl :: ToJson a => a -> BSL.ByteString
serialiseJsonBsl = BSL.fromStrict . serialiseJsonBs
{-# INLINE serialiseJsonBsl #-}
-- | Serialise json to a strict 'T.Text'
serialiseJsonT :: ToJson a => a -> T.Text
serialiseJsonT = T.decodeUtf8 . serialiseJsonBs
{-# INLINE serialiseJsonT #-}