pipes-aeson 0.1.0.0 → 0.2.0
raw patch · 8 files changed
+283/−452 lines, 8 filesdep +transformersdep ~attoparsecdep ~pipesdep ~pipes-attoparsecPVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependency ranges changed: attoparsec, pipes, pipes-attoparsec, pipes-parse
API changes (from Hackage documentation)
- Control.Proxy.Aeson: Array :: !Array -> TopLevelValue
- Control.Proxy.Aeson: Object :: !Object -> TopLevelValue
- Control.Proxy.Aeson: ParserError :: ParsingError -> DecodingError
- Control.Proxy.Aeson: ValueError :: String -> DecodingError
- Control.Proxy.Aeson: data DecodingError
- Control.Proxy.Aeson: data TopLevelValue
- Control.Proxy.Aeson: decode :: (Monad m, Proxy p, FromJSON r) => EitherP DecodingError (StateP [ByteString] p) () (Maybe ByteString) y' y m r
- Control.Proxy.Aeson: decodeD :: (Monad m, Proxy p, FromJSON b) => () -> Pipe (EitherP DecodingError (StateP [ByteString] p)) (Maybe ByteString) b m ()
- Control.Proxy.Aeson: encode :: (Proxy p, Monad m) => TopLevelValue -> p x' x () ByteString m ()
- Control.Proxy.Aeson: encodeD :: (Proxy p, Monad m) => () -> Pipe p TopLevelValue ByteString m r
- Control.Proxy.Aeson: fromValue :: (Monad m, Proxy p, FromJSON r) => x -> EitherP String p x Value y' y m r
- Control.Proxy.Aeson: fromValueD :: (Monad m, Proxy p, FromJSON b) => x -> EitherP String p x Value x b m r
- Control.Proxy.Aeson: instance Eq TopLevelValue
- Control.Proxy.Aeson: instance FromJSON TopLevelValue
- Control.Proxy.Aeson: instance Show TopLevelValue
- Control.Proxy.Aeson: instance ToJSON TopLevelValue
- Control.Proxy.Aeson: parseValue :: (Monad m, Proxy p) => EitherP ParsingError (StateP [ByteString] p) () (Maybe ByteString) y' y m TopLevelValue
- Control.Proxy.Aeson: parseValueD :: (Monad m, Proxy p) => () -> Pipe (EitherP ParsingError (StateP [ByteString] p)) (Maybe ByteString) TopLevelValue m ()
- Control.Proxy.Aeson: toTopLevelValue :: ToJSON a => a -> Maybe TopLevelValue
- Control.Proxy.Aeson.Unsafe: ParserError :: ParsingError -> DecodingError
- Control.Proxy.Aeson.Unsafe: ValueError :: String -> DecodingError
- Control.Proxy.Aeson.Unsafe: data DecodingError
- Control.Proxy.Aeson.Unsafe: decode :: (Monad m, Proxy p, FromJSON r) => EitherP DecodingError (StateP [ByteString] p) () (Maybe ByteString) y' y m r
- Control.Proxy.Aeson.Unsafe: decodeD :: (Monad m, Proxy p, FromJSON b) => () -> Pipe (EitherP DecodingError (StateP [ByteString] p)) (Maybe ByteString) b m ()
- Control.Proxy.Aeson.Unsafe: encode :: (Proxy p, Monad m, ToJSON a) => a -> p x' x () ByteString m ()
- Control.Proxy.Aeson.Unsafe: encodeD :: (Proxy p, Monad m, ToJSON a) => () -> Pipe p a ByteString m r
- Control.Proxy.Aeson.Unsafe: parseValue :: (Monad m, Proxy p) => EitherP ParsingError (StateP [ByteString] p) () (Maybe ByteString) y' y m Value
- Control.Proxy.Aeson.Unsafe: parseValueD :: (Monad m, Proxy p) => () -> Pipe (EitherP ParsingError (StateP [ByteString] p)) (Maybe ByteString) Value m ()
+ Pipes.Aeson: ParserError :: ParsingError -> DecodingError
+ Pipes.Aeson: ValueError :: String -> DecodingError
+ Pipes.Aeson: data DecodingError
+ Pipes.Aeson: decode :: (Monad m, FromJSON b) => StateT (Producer ByteString m r) m (Either DecodingError (Int, b))
+ Pipes.Aeson: decodeMany :: (Monad m, FromJSON b) => Producer ByteString m r -> Producer (Int, b) m (Either (DecodingError, Producer ByteString m r) r)
+ Pipes.Aeson: encode :: Monad m => Either Object Array -> Producer ByteString m ()
+ Pipes.Aeson.Unsafe: decode :: (Monad m, FromJSON b) => StateT (Producer ByteString m r) m (Either DecodingError (Int, b))
+ Pipes.Aeson.Unsafe: decodeMany :: (Monad m, FromJSON b) => Producer ByteString m r -> Producer (Int, b) m (Either (DecodingError, Producer ByteString m r) r)
+ Pipes.Aeson.Unsafe: encode :: (Monad m, ToJSON a) => a -> Producer ByteString m ()
Files
- NEWS +8/−0
- pipes-aeson.cabal +12/−12
- src/Control/Proxy/Aeson.hs +0/−274
- src/Control/Proxy/Aeson/Internal.hs +0/−63
- src/Control/Proxy/Aeson/Unsafe.hs +0/−103
- src/Pipes/Aeson.hs +99/−0
- src/Pipes/Aeson/Internal.hs +104/−0
- src/Pipes/Aeson/Unsafe.hs +60/−0
NEWS view
@@ -1,3 +1,11 @@+# 0.2.0++* Version compatible with `pipes-4.0.0` and `pipes-parse-2.0.0`.++* API radically changed. Removed `parseValue`, `fromValue` and+ `TopLevelValue`. Other things renamed.++ # 0.1.0.0 * First version.
pipes-aeson.cabal view
@@ -1,5 +1,5 @@ name: pipes-aeson-version: 0.1.0.0+version: 0.2.0 license: BSD3 license-file: LICENSE copyright: Copyright (c) Renzo Carbonara 2013@@ -12,11 +12,10 @@ category: Pipes, Parser build-type: Simple cabal-version: >=1.8-synopsis: Encode and decode JSON streams using aeson and pipes.+synopsis: Encode and decode JSON streams using Aeson and Pipes. extra-source-files: README.md PEOPLE NEWS description:- Utilities to encode and decode JSON streams using @aeson@ and @pipes@,- possibly interleaving other stream effects too.+ Utilities to encode and decode Pipes streams of JSON. . See the @NEWS@ file in the source distribution to learn about any important changes between version.@@ -27,16 +26,17 @@ library hs-source-dirs: src- exposed-modules: Control.Proxy.Aeson- Control.Proxy.Aeson.Unsafe- other-modules: Control.Proxy.Aeson.Internal+ exposed-modules: Pipes.Aeson+ Pipes.Aeson.Unsafe+ other-modules: Pipes.Aeson.Internal build-depends: aeson (>=0.6.1 && < 0.7)- , attoparsec- , base (>=4.5 && < 5.0)- , pipes (>=3.3 && <3.4)- , pipes-attoparsec (>=0.2 && <0.3)- , pipes-parse (>=1.0 && <1.1)+ , attoparsec (>=0.10 && <0.11)+ , base (>=4.5 && <5.0)+ , pipes (>=4.0 && <4.1)+ , pipes-attoparsec (>=0.3 && <0.4)+ , pipes-parse (>=2.0 && <2.1) , bytestring (>=0.9.2.1)+ , transformers (>=0.2 && <0.4) ghc-options: -Wall -O2 ghc-prof-options: -fprof-auto
− src/Control/Proxy/Aeson.hs
@@ -1,274 +0,0 @@--- | This module allows you to encode and decode JSON values flowing downstream--- through Pipes streams, possibly interleaving other stream effects.------ This module builds on top of the @pipes-parse@ and @pipes-attoparsec@--- packages and assumes you have read "Control.Proxy.Parse.Tutorial".--module Control.Proxy.Aeson- ( -- * Top level JSON values- -- $top-level-value- TopLevelValue(..)- , toTopLevelValue- -- * Encoding- -- $encoding- , encode- , encodeD- -- * Decoding- -- $decoding- , decode- , decodeD- -- ** Lower level parsing- , parseValue- , parseValueD- , fromValue- , fromValueD- -- * Types- , I.DecodingError(..)- ) where---import Control.Monad (unless)-import qualified Control.Proxy as P-import qualified Control.Proxy.Aeson.Internal as I-import qualified Control.Proxy.Aeson.Unsafe as U-import qualified Control.Proxy.Attoparsec as PA-import qualified Control.Proxy.Trans.Either as P-import qualified Control.Proxy.Trans.State as P-import qualified Data.Aeson as Ae-import qualified Data.ByteString.Char8 as B-import Data.Maybe (fromJust)------------------------------------------------------------------------------------- $top-level-value------ The JSON RFC-4627 standard only allows 'Ae.Array' or 'Ae.Object' values as--- top-level. The 'TopLevelValue' type used throughout this module in--- replacement of Aesons's 'Ae.Value' enforces that restricion in a type-safe--- manner.------ If you want to ignore the standard and encode or decode any 'Ae.Value', then--- use the facilities exported by the "Control.Proxy.Aeson.Unsafe" module.------ * You may use the 'toTopLevelValue' function to convert any 'Ae.ToJSON'--- instance to a 'TopLevelValue', if possible. Remember that 'Ae.Value' is--- one such instance.------ * Use the 'Ae.toJSON' method on a 'TopLevelValue' to obtain its underlying--- 'Ae.Value' representation.----- | A JSON top-level value must be an 'Ae.Object' or an 'Ae.Array', according--- to RFC-4627.-data TopLevelValue- = Object !Ae.Object- | Array !Ae.Array- deriving (Show, Eq)--instance Ae.ToJSON TopLevelValue where- toJSON (Object o) = Ae.Object o- toJSON (Array a) = Ae.Array a--instance Ae.FromJSON TopLevelValue where- parseJSON (Ae.Object o) = return (Object o)- parseJSON (Ae.Array a) = return (Array a)- parseJSON _ = fail "Not a valid top-level value"---- | Converts the given 'Ae.ToJSON' instance to a 'TopLevelValue' as long as its--- 'Ae.Value' representation is one of 'Ae.Object' or 'Ae.Array', otherwise--- 'Nothing'. Remember that 'Ae.Value' itself is a 'Ae.ToJSON' instance.-toTopLevelValue :: Ae.ToJSON a => a -> Maybe TopLevelValue-toTopLevelValue = \a ->- case Ae.toJSON a of- Ae.Object x -> Just (Object x)- Ae.Array x -> Just (Array x)- _ -> Nothing-{-# INLINABLE toTopLevelValue #-}------------------------------------------------------------------------------------- $encoding------ There are two different JSON encoding facilities exported by this module, and--- choosing between them is easy: If you need to interleave JSON encoding--- with other stream effects you must use 'encode', otherwise you may use the--- simpler 'encodeD'.------ Both encoding proxies enforce the JSON RFC-4627 requirement that top-level--- values are either 'Ae.Array's or 'Ae.Object's, as witnessed by the--- 'TopLevelValue' type. However, if you need to ignore this requirement you may--- use the similar encoding proxies exported by the "Control.Proxy.Aeson.Unsafe"--- module.---- | Encodes the given 'TopLevelValue' as JSON and sends it downstream, possibly--- in more than one 'BS.ByteString' chunk.-encode :: (P.Proxy p, Monad m) => TopLevelValue -> p x' x () B.ByteString m ()-encode = U.encode-{-# INLINABLE encode #-}---- | Encodes 'TopLevelValue's flowing downstream as JSON, each in possibly more--- than one 'BS.ByteString' chunk, and sends each chunk downstream.-encodeD :: (P.Proxy p, Monad m) => () -> P.Pipe p TopLevelValue B.ByteString m r-encodeD = U.encodeD-{-# INLINABLE encodeD #-}------------------------------------------------------------------------------------- $decoding------ Decoding a JSON value as a Haskell type in involves two different steps:------ * Parsing a raw JSON 'B.ByteString' into a 'TopLevelValue'.------ * Converting the obtained 'TopLevelValue' to the desired type, which must be--- a 'Ae.FromJSON' instance.------ Any of those steps can fail, and in case of errors, the 'I.DecodingError'--- type explicitly states at which the step the error happened.------ There are two different JSON decoding facilities exported by this module,--- both perform those steps at once. Choosing between them is easy: If you--- need to interleave JSON decoding with other stream effects you must use--- 'decode', otherwise you may use the simpler 'decodeD'.------ These proxies use the 'P.EitherP' proxy transformer to report decoding--- errors, you might use any of the facilities exported by--- "Control.Proxy.Trans.Either" to recover from them.------ If you prefer to perform each of the decoding steps separately, you--- could use instead the 'parseValue', 'parseValueD', 'fromValue' or--- 'fromValueD' proxies.----- | Decodes one JSON value flowing downstream.------ * In case of decoding errors, a 'I.DecodingError' exception is thrown in--- the 'Pe.EitherP' proxy transformer.------ * Requests more input from upstream using 'Pa.draw' when needed.------ * /Do not/ use this proxy if your stream has leading empty chunks or--- whitespace, otherwise you may get unexpected parsing errors.------ Here is an example parsing loop that allows interleaving stream effects--- together with 'decode':------ @--- loop = do--- -- Skip any leading whitespace and check that we haven't reached EOF.--- eof <- 'P.liftP' $ 'Control.Proxy.ByteString.dropWhile' 'Data.Char.isSpace' >> 'PA.isEndOfParserInput'--- 'unless' eof $ do--- -- 1. Possibly perform some stream effects here.--- -- 2. Decode one JSON element from the stream.--- exampleElement <- 'decode'--- -- 3. Do something with exampleElement and possibly perform--- -- some more stream effects.--- -- 4. Start all over again.--- loop--- @-decode- :: (Monad m, P.Proxy p, Ae.FromJSON r)- => P.EitherP I.DecodingError (P.StateP [B.ByteString] p)- () (Maybe B.ByteString) y' y m r-decode = do- ev <- P.liftP . P.runEitherP $ PA.parse Ae.json'- case ev of- Left e -> P.throw (I.ParserError e)- Right v ->- case Ae.fromJSON v of- Ae.Error e -> P.throw (I.ValueError e)- Ae.Success r -> return r-{-# INLINABLE decode #-}----- | Decodes consecutive JSON values flowing downstream until end of input.------ * In case of decoding errors, a 'I.DecodingError' exception is thrown in--- the 'Pe.EitherP' proxy transformer.------ * Requests more input from upstream using 'Pa.draw' when needed.------ * Empty input chunks flowing downstream and whitespace in between JSON--- values will be discarded.-decodeD- :: (Monad m, P.Proxy p, Ae.FromJSON b)- => ()- -> P.Pipe (P.EitherP I.DecodingError (P.StateP [B.ByteString] p))- (Maybe B.ByteString) b m ()-decodeD = \() -> loop where- loop = do- eof <- P.liftP $ I.skipSpace >> PA.isEndOfParserInput- unless eof $ decode >>= P.respond >> loop-{-# INLINABLE decodeD #-}-------------------------------------------------------------------------------------- | Parses a JSON value flowing downstream into a 'TopLevelValue'.------ * In case of parsing errors, a 'PA.ParsingError' exception is thrown in--- the 'Pe.EitherP' proxy transformer.------ * Requests more input from upstream using 'Pa.draw' when needed.------ * /Do not/ use this proxy if your stream has leading empty chunks or--- whitespace, otherwise you may get unexpected parsing errors.------ See the documentation of 'decode' for an example of how to interleave--- other stream effects together with this proxy.-parseValue- :: (Monad m, P.Proxy p)- => P.EitherP PA.ParsingError (P.StateP [B.ByteString] p)- () (Maybe B.ByteString) y' y m TopLevelValue-parseValue = return . fromJust . toTopLevelValue =<< PA.parse Ae.json'-{-# INLINABLE parseValue #-}----- | Parses consecutive JSON values flowing downstream as 'TopLevelValue's,--- until end of input.------ * In case of parsing errors, a 'I.DecodingError' exception is thrown in--- the 'Pe.EitherP' proxy transformer.------ * Requests more input from upstream using 'Pa.draw' when needed.------ * Empty input chunks flowing downstream and whitespace in between JSON--- values will be discarded.-parseValueD- :: (Monad m, P.Proxy p)- => ()- -> P.Pipe (P.EitherP PA.ParsingError (P.StateP [B.ByteString] p))- (Maybe B.ByteString) TopLevelValue m ()-parseValueD = \() -> loop where- loop = do- eof <- P.liftP $ I.skipSpace >> PA.isEndOfParserInput- unless eof $ parseValue >>= P.respond >> loop-{-# INLINABLE parseValueD #-}-------------------------------------------------------------------------------------- | Converts any 'Ae.Value' flowing downstream to a 'Ae.FromJSON' instance.------ * In case of parsing errors, a 'String' exception holding the value provided--- by Aeson's 'Ae.Error' is thrown in the 'Pe.EitherP' proxy transformer.------ See the documentation of 'decode' for an example of how to interleave--- other stream effects together with this proxy.-fromValue- :: (Monad m, P.Proxy p, Ae.FromJSON r)- => x -> P.EitherP String p x Ae.Value y' y m r-fromValue = \x -> do- v <- P.request x- case Ae.fromJSON v of- Ae.Error e -> P.throw e- Ae.Success r -> return r-{-# INLINABLE fromValue #-}----- | Converts any 'Ae.Value's flowing downstream to 'Ae.FromJSON' instances and--- forwards them downstream.------ * In case of parsing errors, a 'String' exception holding the value provided--- by Aeson's 'Ae.Error' is thrown in the 'Pe.EitherP' proxy transformer.-fromValueD- :: (Monad m, P.Proxy p, Ae.FromJSON b)- => x -> P.EitherP String p x Ae.Value x b m r-fromValueD = fromValue P.\>\ P.pull-{-# INLINABLE fromValueD #-}-
− src/Control/Proxy/Aeson/Internal.hs
@@ -1,63 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}---- | This module provides internal utilities and it is likely--- to be modified in backwards-incompatible ways in the future.------ Use the stable API exported by the "Control.Proxy.Aeson" module instead.-module Control.Proxy.Aeson.Internal- ( DecodingError(..)- , skipSpace- , fromLazy- ) where--import qualified Control.Proxy.Attoparsec as PA-import qualified Control.Proxy.Trans.State as P-import Control.Exception (Exception)-import qualified Control.Proxy as P-import qualified Control.Proxy.Parse as Pp-import qualified Data.ByteString.Char8 as B-import qualified Data.ByteString.Lazy.Internal as BLI-import qualified Data.Char as Char-import Data.Data (Data, Typeable)-import Data.Function (fix)-------------------------------------------------------------------------------------- | An error while decoding a JSON value.-data DecodingError- = ParserError PA.ParsingError- -- ^An Attoparsec error that happened while parsing the raw JSON string.- | ValueError String- -- ^An Aeson error that happened while trying to convert a- -- 'Data.Aeson.Value' to an 'A.FromJSON' instance, as reported by- -- 'Data.Aeson.Error'.- deriving (Show, Eq, Data, Typeable)--instance Exception DecodingError------------------------------------------------------------------------------------- XXX we define the following proxies here until 'pipes-bytestring' is released---- | Consumes and discards leading 'I.ParserInput' characters from upstream as--- long as the given predicate holds 'True'.-skipSpace- :: (Monad m, P.Proxy p)- => P.StateP [B.ByteString] p () (Maybe B.ByteString) y' y m ()-skipSpace = fix $ \loop -> do- ma <- Pp.draw- case ma of- Nothing -> return ()- Just a -> do- let a' = B.dropWhile Char.isSpace a- if B.null a'- then loop- else Pp.unDraw a'-{-# INLINABLE skipSpace #-}---- Sends each of the 'BLI.ByteString''s strict chunks downstream.-fromLazy- :: (Monad m, P.Proxy p)- => BLI.ByteString -> p x' x y' B.ByteString m ()-fromLazy =- P.runIdentityP . BLI.foldrChunks (\e a -> P.respond e >> a) (return ())-{-# INLINABLE fromLazy #-}
− src/Control/Proxy/Aeson/Unsafe.hs
@@ -1,103 +0,0 @@--- | This module exports facilities similar to those exported by the--- "Control.Proxy.Aeson" module, except they do not restrict the 'Ae.Value's--- that might be encoded or decoded to be just valid top-level values. That is,--- not only 'Ae.Object's or 'Ae.Array's, according to to the RFC-4627 JSON--- standard.--module Control.Proxy.Aeson.Unsafe- (-- * Encoding- -- $encoding- encode- , encodeD- -- * Decoding- -- $decoding- , decode- , decodeD- -- ** Lower level parsing- , parseValue- , parseValueD- -- * Types- , I.DecodingError(..)- ) where--import Control.Monad (unless)-import qualified Control.Proxy as P-import qualified Control.Proxy.Aeson.Internal as I-import qualified Control.Proxy.Attoparsec as PA-import qualified Control.Proxy.Trans.Either as P-import qualified Control.Proxy.Trans.State as P-import qualified Data.Aeson as Ae-import qualified Data.Aeson.Parser as Ae (value')-import qualified Data.ByteString.Char8 as B-------------------------------------------------------------------------------------- | Like 'Control.Proxy.Aeson.encode', except it accepts any 'Ae.ToJSON'--- instance.-encode- :: (P.Proxy p, Monad m, Ae.ToJSON a) => a -> p x' x () B.ByteString m ()-encode = I.fromLazy . Ae.encode-{-# INLINABLE encode #-}---- | Like 'Control.Proxy.Aeson.encodeD', except it accepts any 'Ae.ToJSON'--- instance.-encodeD- :: (P.Proxy p, Monad m, Ae.ToJSON a) => () -> P.Pipe p a B.ByteString m r-encodeD = P.pull P./>/ encode-{-# INLINABLE encodeD #-}-------------------------------------------------------------------------------------- | Like 'Control.Proxy.Aeson.decode', except it will decode any 'Ae.ToJSON'--- instance.-decode- :: (Monad m, P.Proxy p, Ae.FromJSON r)- => P.EitherP I.DecodingError (P.StateP [B.ByteString] p)- () (Maybe B.ByteString) y' y m r-decode = do- ev <- P.liftP . P.runEitherP $ PA.parse Ae.value'- case ev of- Left e -> P.throw (I.ParserError e)- Right v ->- case Ae.fromJSON v of- Ae.Error e -> P.throw (I.ValueError e)- Ae.Success r -> return r-{-# INLINABLE decode #-}---- | Like 'Control.Proxy.Aeson.decodeD', except it will decode any 'Ae.ToJSON'--- instance.-decodeD- :: (Monad m, P.Proxy p, Ae.FromJSON b)- => ()- -> P.Pipe (P.EitherP I.DecodingError (P.StateP [B.ByteString] p))- (Maybe B.ByteString) b m ()-decodeD = \() -> loop where- loop = do- eof <- P.liftP $ I.skipSpace >> PA.isEndOfParserInput- unless eof $ decode >>= P.respond >> loop-{-# INLINABLE decodeD #-}-------------------------------------------------------------------------------------- | Like 'Control.Proxy.Aeson.parseValue', except it will parse into any--- 'Ae.Value'.-parseValue- :: (Monad m, P.Proxy p)- => P.EitherP PA.ParsingError (P.StateP [B.ByteString] p)- () (Maybe B.ByteString) y' y m Ae.Value-parseValue = PA.parse Ae.value'-{-# INLINABLE parseValue #-}---- | Like 'Control.Proxy.Aeson.parseValueD', except it will parse into any--- 'Ae.Value'.-parseValueD- :: (Monad m, P.Proxy p)- => ()- -> P.Pipe (P.EitherP PA.ParsingError (P.StateP [B.ByteString] p))- (Maybe B.ByteString) Ae.Value m ()-parseValueD = \() -> loop where- loop = do- eof <- P.liftP $ I.skipSpace >> PA.isEndOfParserInput- unless eof $ parseValue >>= P.respond >> loop-{-# INLINABLE parseValueD #-}-
+ src/Pipes/Aeson.hs view
@@ -0,0 +1,99 @@+{-# 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.++module Pipes.Aeson+ ( -- * Encoding+ encode+ -- * Decoding+ -- $decoding+ , decode+ , decodeMany+ -- * Types+ , I.DecodingError(..)+ ) where++import Pipes+import qualified Pipes.Aeson.Internal as I+import qualified Pipes.Aeson.Unsafe as U+import qualified Pipes.Attoparsec as PA+import qualified Control.Monad.Trans.State.Strict as S+import qualified Data.Aeson as Ae+import qualified Data.ByteString.Char8 as B++--------------------------------------------------------------------------------++-- | Encode an 'Ae.Array' or 'Ae.Object' as JSON and send it 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 this function restricts its input to them. If you+-- prefer to ignore the standard and encode any 'Ae.Value', then use 'U.encode'+-- from the "Pipes.Aeson.Unsafe" module.+encode :: Monad m => Either Ae.Object Ae.Array -> Producer B.ByteString m ()+encode = either U.encode U.encode+{-# INLINABLE encode #-}++--------------------------------------------------------------------------------+-- $decoding+--+-- Decoding JSON as a Haskell value in 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 the step it appened.+++-- | Decodes an 'Ae.Object' or 'Ae.Array' JSON value from the underlying state.+--+-- /Do not/ use this function if the underlying 'Producer' has leading empty+-- chunks or whitespace, otherwise you may get unexpected parsing errors.+--+-- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level+-- entities, which is why this 'Producer' 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.Unsafe" module.+decode+ :: (Monad m, Ae.FromJSON b)+ => S.StateT (Producer B.ByteString m r) m (Either I.DecodingError (Int, b))+decode = do+ ev <- PA.parse Ae.json'+ return $ do+ case ev of+ Left e -> Left (I.ParserError e)+ Right (len, v) -> do+ case Ae.fromJSON v of+ Ae.Error e -> Left (I.ValueError e)+ Ae.Success b -> Right (len, b)+{-# INLINABLE decode #-}++-- | Continuously 'decode' the JSON output from the given 'Producer', sending+-- downstream pairs of each successfully decoded entity together with the number+-- of bytes consumed in order to produce it. Whitespace in between JSON content+-- is ignored.+--+-- This 'Producer' runs until it either runs out of input or until a decoding+-- failure occurs, in which case it returns 'Left' with a 'I.DecodingError' and+-- a 'Producer' with any leftovers. You can use 'P.errorP' to turn the 'Either'+-- return value into an 'Control.Monad.Trans.Error.ErrorT' monad transformer.+--+-- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level+-- entities, which is why this 'Producer' restricts its output to them. If you+-- prefer to ignore the standard and decode any 'Ae.Value', then use+-- 'U.decodeMany' from the "Pipes.Aeson.Unsafe" module.+decodeMany+ :: (Monad m, Ae.FromJSON b)+ => Producer B.ByteString m r -- ^Producer from which to draw JSON.+ -> Producer (Int, b) m+ (Either (I.DecodingError, Producer B.ByteString m r) r)+decodeMany = I.consecutively decode+{-# INLINABLE decodeMany #-}+
+ src/Pipes/Aeson/Internal.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}++-- | This module provides internal utilities and it is likely+-- to be modified in backwards-incompatible ways in the future.+--+-- Use the stable API exported by the "Pipes.Aeson" module instead.+module Pipes.Aeson.Internal+ ( DecodingError(..)+ , consecutively+ , skipSpace+ , fromLazy+ ) where++import Control.Exception (Exception)+import qualified Control.Monad.Trans.Error as E+import qualified Control.Monad.Trans.State.Strict as S+import qualified Data.Aeson as Ae+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Internal as BLI+import qualified Data.Char as Char+import Data.Data (Data, Typeable)+import Pipes+import qualified Pipes.Attoparsec as PA+import qualified Pipes.Parse as Pp+import qualified Pipes.Lift as P++--------------------------------------------------------------------------------++-- | An error while decoding a JSON value.+data DecodingError+ = ParserError PA.ParsingError+ -- ^An Attoparsec error that happened while parsing the raw JSON string.+ | ValueError String+ -- ^An Aeson error that happened while trying to convert a+ -- 'Data.Aeson.Value' to an 'A.FromJSON' instance, as reported by+ -- 'Data.Aeson.Error'.+ deriving (Show, Eq, Data, Typeable)++instance Exception DecodingError+instance E.Error DecodingError+instance Monad m => E.Error (DecodingError, Producer a m r)++--------------------------------------------------------------------------------++-- | Consecutively parse 'b' elements from the given 'Producer' using the given+-- parser (such as 'Pipes.Aeson.decode' or 'Pipes.Aeson.parseValue'), skipping+-- any leading whitespace each time.+--+-- This 'Producer' runs until it either runs out of input or until a decoding+-- failure occurs, in which case it returns 'Left' with a 'I.DecodingError' and+-- a 'Producer' with any leftovers. You can use 'P.errorP' to turn the 'Either'+-- return value into an 'Control.Monad.Trans.Error.ErrorT' monad transformer.+consecutively+ :: (Monad m, Ae.FromJSON b)+ => S.StateT (Producer B.ByteString m r) m (Either DecodingError (Int, b))+ -> Producer B.ByteString m r -- ^Producer from which to draw JSON.+ -> Producer (Int, b) m+ (Either (DecodingError, Producer B.ByteString m r) r)+consecutively parser = \src -> do+ (er, src') <- P.runStateP src prod+ return $ case er of+ Left e -> Left (e, src')+ Right r -> Right r+ where+ prod = do+ eof <- lift (skipSpace >> PA.isEndOfParserInput)+ if eof+ then do+ ra <- lift Pp.draw+ case ra of+ Left r -> return (Right r)+ Right _ -> error "Pipes.Aeson.parseMany: impossible!!"+ else do+ eb <- lift parser+ case eb of+ Left e -> return (Left e)+ Right b -> yield b >> prod+{-# INLINABLE consecutively #-}++--------------------------------------------------------------------------------++-- XXX we define the following proxies here until 'pipes-bytestring' is released++-- | Consumes and discards leading 'I.ParserInput' characters from upstream as+-- long as the given predicate holds 'True'.+skipSpace :: Monad m => S.StateT (Producer B.ByteString m r) m ()+skipSpace = do+ ma <- Pp.draw+ case ma of+ Left _ -> return ()+ Right a -> do+ let a' = B.dropWhile Char.isSpace a+ if B.null a'+ then skipSpace+ else Pp.unDraw a'+{-# INLINABLE skipSpace #-}++-- Sends each of the 'BLI.ByteString''s strict chunks downstream.+fromLazy :: Monad m => BLI.ByteString -> Producer' B.ByteString m ()+fromLazy = BLI.foldrChunks (\e a -> yield e >> a) (return ())+{-# INLINABLE fromLazy #-}
+ src/Pipes/Aeson/Unsafe.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE RankNTypes #-}++-- | This module exports facilities similar to those exported by the+-- "Pipes.Aeson" module, except they do not restrict the 'Ae.Value's+-- that might be encoded or decoded to be just valid top-level values. That is,+-- not only 'Ae.Object's or 'Ae.Array's, according to to the RFC-4627 JSON+-- standard.++module Pipes.Aeson.Unsafe+ ( -- * Encoding+ encode+ -- * Decoding+ , decode+ , decodeMany+ ) where++import Pipes+import qualified Pipes.Aeson.Internal as I+import qualified Pipes.Attoparsec as PA+import qualified Control.Monad.Trans.State.Strict as S+import qualified Data.Aeson as Ae+import qualified Data.Aeson.Parser as Ae (value')+import qualified Data.ByteString as B++--------------------------------------------------------------------------------++-- | Like 'Pipes.Aeson.encode', except it accepts any 'Ae.ToJSON' instance,+-- not just 'Ae.Array' or 'Ae.Object'.+encode :: (Monad m, Ae.ToJSON a) => a -> Producer B.ByteString m ()+encode = I.fromLazy . Ae.encode+{-# INLINABLE encode #-}++--------------------------------------------------------------------------------++-- | Like 'Pipes.Aeson.decode', except it will decode any 'Ae.ToJSON' instance,+-- not just 'Ae.Array' or 'Ae.Object'.+decode+ :: (Monad m, Ae.FromJSON b)+ => S.StateT (Producer B.ByteString m r) m (Either I.DecodingError (Int, b))+decode = do+ ev <- PA.parse Ae.value'+ return $ do+ case ev of+ Left e -> Left (I.ParserError e)+ Right (len, v) -> do+ case Ae.fromJSON v of+ Ae.Error e -> Left (I.ValueError e)+ Ae.Success b -> Right (len, b)+{-# INLINABLE decode #-}++-- | Like 'Pipes.Aeson.decodeMany', except it will decode any 'Ae.ToJSON'+-- instance, not just 'Ae.Array' or 'Ae.Object'.+decodeMany+ :: (Monad m, Ae.FromJSON b)+ => Producer B.ByteString m r -- ^Producer from which to draw JSON.+ -> Producer (Int, b) m+ (Either (I.DecodingError, Producer B.ByteString m r) r)+decodeMany = I.consecutively decode+{-# INLINABLE decodeMany #-}+