diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -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.
diff --git a/pipes-aeson.cabal b/pipes-aeson.cabal
--- a/pipes-aeson.cabal
+++ b/pipes-aeson.cabal
@@ -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
diff --git a/src/Control/Proxy/Aeson.hs b/src/Control/Proxy/Aeson.hs
deleted file mode 100644
--- a/src/Control/Proxy/Aeson.hs
+++ /dev/null
@@ -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 &#x3c;- '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 #-}
-
diff --git a/src/Control/Proxy/Aeson/Internal.hs b/src/Control/Proxy/Aeson/Internal.hs
deleted file mode 100644
--- a/src/Control/Proxy/Aeson/Internal.hs
+++ /dev/null
@@ -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 #-}
diff --git a/src/Control/Proxy/Aeson/Unsafe.hs b/src/Control/Proxy/Aeson/Unsafe.hs
deleted file mode 100644
--- a/src/Control/Proxy/Aeson/Unsafe.hs
+++ /dev/null
@@ -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 #-}
-
diff --git a/src/Pipes/Aeson.hs b/src/Pipes/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/Aeson.hs
@@ -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 #-}
+
diff --git a/src/Pipes/Aeson/Internal.hs b/src/Pipes/Aeson/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/Aeson/Internal.hs
@@ -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 #-}
diff --git a/src/Pipes/Aeson/Unsafe.hs b/src/Pipes/Aeson/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/Aeson/Unsafe.hs
@@ -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 #-}
+
