{-# LANGUAGE RankNTypes #-} -- | This module allows you to encode and decode JSON values flowing downstream -- through Pipes streams. -- -- This module builds on top of the @aeson@, @pipes@ and @pipes-parse@ -- libraries, and assumes you know how to use them. Please read the examples -- in "Pipes.Parse.Tutorial" to understand how to use these functions. -- -- In this module, the following type synonym compatible with the @lens@, -- @lens-family@ and @lens-family-core@ libraries is used but not exported: -- -- @ -- type Lens' s a = forall f . 'Functor' f => (a -> f a) -> (s -> f s) -- @ module Pipes.Aeson ( -- * Encoding -- $encoding encodeArray , encodeObject -- * Decoding -- $decoding , decode , decoded -- ** Including lengths , decodeL , decodedL -- * Types , I.DecodingError(..) ) where import Control.Monad (liftM) import qualified Data.Aeson as Ae import qualified Data.ByteString.Char8 as B import Pipes import qualified Pipes.Aeson.Internal as I import qualified Pipes.Aeson.Unchecked as U import qualified Pipes.Parse as Pipes -------------------------------------------------------------------------------- -- $encoding -- -- Encode 'Ae.Array' or 'Ae.Object' values as JSON and send them downstream, -- possibly in more than one 'B.ByteString' chunk. -- -- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level -- entities, which is why these functions restrict their input to them. If you -- prefer to ignore the standard and encode any 'Ae.Value', then use 'U.encode' -- from the "Pipes.Aeson.Unchecked" module. -- -- | Encode an 'Ae.Object' as JSON and send it downstream, -- -- /Hint:/ You can easily turn this 'Producer'' into a 'Pipe' that encodes -- 'Ae.Object' values as JSON as they flow downstream using: -- -- @ -- 'for' 'cat' 'encodeObject' :: 'Monad' m => 'Pipe' 'Ae.Object' 'B.ByteString' m r -- @ encodeObject :: Monad m => Ae.Object -> Producer' B.ByteString m () encodeObject = U.encode {-# INLINABLE encodeObject #-} {-# RULES "p >-> for cat encodeObject" forall p . p >-> for cat encodeObject = for p encodeObject #-} -- | Encode an 'Ae.Array' as JSON and send it downstream, -- -- /Hint:/ You can easily turn this 'Producer'' into a 'Pipe' that encodes -- 'Ae.Array' values as JSON as they flow downstream using: -- -- @ -- 'for' 'cat' 'encodeArray' :: 'Monad' m => 'Pipe' 'Ae.Array' 'B.ByteString' m r -- @ encodeArray :: Monad m => Ae.Array -> Producer' B.ByteString m () encodeArray = U.encode {-# INLINABLE encodeArray #-} {-# RULES "p >-> for cat encodeArray" forall p . p >-> for cat encodeArray = for p encodeArray #-} -------------------------------------------------------------------------------- -- $decoding -- -- Decoding JSON as a Haskell value involves two different steps: -- -- * Parsing a raw JSON 'B.ByteString' into an 'Ae.Object' or an 'Ae.Array'. -- -- * Converting the obtained 'Ae.Object' or 'Ae.Array' to the desired -- 'Ae.FromJSON' instance. -- -- Any of those steps can fail, in which case a 'I.DecodingError' will report -- the precise error and at which step it happened. -- | Decodes an 'Ae.Object' or 'Ae.Array' JSON value from the underlying state. -- -- It returns 'Nothing' if the underlying 'Producer' is exhausted, otherwise -- it returns either the decoded entity or a 'I.DecodingError' in case of error. -- -- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level -- entities, which is why this 'Pipes.Parser' restricts its output to them. If -- you prefer to ignore the standard and decode any 'Ae.Value', then use -- 'U.decode' from the "Pipes.Aeson.Unchecked" module. decode :: (Monad m, Ae.FromJSON a) => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError a)) decode = fmap (fmap snd) `liftM` decodeL {-# INLINABLE decode #-} -- | Like 'decode', except it also returns the length of JSON input that was -- consumed in order to obtain the value, not including the length of whitespace -- before nor after the parsed JSON input. decodeL :: (Monad m, Ae.FromJSON a) => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError (Int, a))) decodeL = I.decodeL Ae.json' {-# INLINABLE decodeL #-} -- | /Improper lens/ that turns a stream of raw JSON input into a stream of -- 'Ae.FromJSON' and back. -- -- By /improper lens/ we mean that in practice you can't expect the -- /Monad Morphism Laws/ to be true when using 'decoded' with -- 'Control.Lens.zoom'. -- -- @ -- 'Control.Lens.zoom' 'decoded' ('return' r) /= 'return' r -- 'Control.Lens.zoom' 'decoded' (m >>= k) /= 'Control.Lens.zoom' m >>= 'Control.Lens.zoom' . f -- @ -- -- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level -- entities, which is why this function restricts its stream values to them. If -- you prefer to ignore the standard and encode or decode any 'Ae.Value', then -- use 'U.decoded' from the "Pipes.Aeson.Unchecked" module. decoded :: (Monad m, Ae.FromJSON a, Ae.ToJSON a) => (Ae.Value -> Either Ae.Object Ae.Array) -- ^ A witness that @a@ can be represented either as an 'Ae.Object' or as -- an 'Ae.Array'. The passed in 'Ae.Value' is @'Ae.toJSON' a@ -> Lens' (Producer B.ByteString m r) (Producer a m (Either (I.DecodingError, Producer B.ByteString m r) r)) decoded f k p0 = fmap _encode (k (I.consecutively decode p0)) where _encode = \p -> do er <- for p (\a -> either encodeObject encodeArray (f (Ae.toJSON a))) case er of Left (_, p') -> p' Right r -> return r {-# INLINABLE decoded #-} -- | Like 'decoded', except it also tags each decoded entity with the length of -- JSON input that was consumed in order to obtain the value, not including the -- length of whitespace between each parsed JSON input. decodedL :: (Monad m, Ae.FromJSON a, Ae.ToJSON a) => (Ae.Value -> Either Ae.Object Ae.Array) -- ^ A witness that @a@ can be represented either as an 'Ae.Object' or as -- an 'Ae.Array'. The passed in 'Ae.Value' is @'Ae.toJSON' a@ -> Lens' (Producer B.ByteString m r) (Producer (Int, a) m (Either (I.DecodingError, Producer B.ByteString m r) r)) decodedL f k p0 = fmap _encode (k (I.consecutively decode p0)) where _encode = \p -> do er <- for p (\(_, a) -> either encodeObject encodeArray (f (Ae.toJSON a))) case er of Left (_, p') -> p' Right r -> return r {-# INLINABLE decodedL #-} -------------------------------------------------------------------------------- -- Internal tools -------------------------------------------------------------- type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s