diff --git a/Yesod/Json.hs b/Yesod/Json.hs
--- a/Yesod/Json.hs
+++ b/Yesod/Json.hs
@@ -1,88 +1,128 @@
-{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE TypeSynonymInstances, OverloadedStrings #-}
 {-# LANGUAGE CPP #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 module Yesod.Json
     ( -- * Convert from a JSON value
       defaultLayoutJson
     , jsonToRepJson
+
       -- * Convert to a JSON value
     , parseJsonBody
-      -- * Compatibility wrapper for old API
-    , Json
-    , jsonScalar
-    , jsonList
-    , jsonMap
+    , parseJsonBody_
+
+      -- * Produce JSON values
+    , J.Value (..)
+    , object
+    , array
+    , (.=)
+
+      -- * Convenience functions
+    , jsonOrRedirect
     ) where
 
-import Yesod.Handler (GHandler)
+import Yesod.Handler (GHandler, waiRequest, lift, invalidArgs, redirect)
 import Yesod.Content
     ( ToContent (toContent), RepHtmlJson (RepHtmlJson), RepHtml (RepHtml)
     , RepJson (RepJson), Content (ContentBuilder)
     )
 import Yesod.Core (defaultLayout, Yesod)
 import Yesod.Widget (GWidget)
+import Yesod.Routes.Class
+import Control.Arrow (second)
+import Control.Applicative ((<$>))
+import Control.Monad (join)
 import qualified Data.Aeson as J
+import Data.Aeson ((.=))
 import qualified Data.Aeson.Encode as JE
 import Data.Aeson.Encode (fromValue)
-import Data.Attoparsec.Enumerator (iterParser)
-import Data.Text (pack)
-import Control.Arrow (first)
-import Control.Monad.Trans.Class (lift)
-#if MIN_VERSION_aeson(0, 4, 0)
-import Data.HashMap.Strict (fromList)
-#else
-import Data.Map (fromList)
-#endif
+import Data.Conduit.Attoparsec (sinkParser)
+import Data.Text (Text, pack)
 import qualified Data.Vector as V
 import Text.Julius (ToJavascript (..))
 import Data.Text.Lazy.Builder (fromLazyText)
 import Data.Text.Lazy.Encoding (decodeUtf8)
-#if MIN_VERSION_aeson(0, 5, 0)
 import Data.Text.Lazy.Builder (toLazyText)
 import qualified Blaze.ByteString.Builder.Char.Utf8 as Blaze
-#endif
+import Data.Conduit (($$))
+import Network.Wai (requestBody, requestHeaders)
+import Network.Wai.Parse (parseHttpAccept)
+import qualified Data.ByteString.Char8 as B8
+import Safe (headMay)
 
 instance ToContent J.Value where
-#if MIN_VERSION_aeson(0, 5, 0)
     toContent = flip ContentBuilder Nothing
               . Blaze.fromLazyText
               . toLazyText
               . fromValue
-#else
-    toContent = flip ContentBuilder Nothing . fromValue
-#endif
 
--- | Provide both an HTML and JSON representation for a piece of data, using
--- the default layout for the HTML output ('defaultLayout').
-defaultLayoutJson :: Yesod master
-                  => GWidget sub master ()
-                  -> J.Value
+-- | Provide both an HTML and JSON representation for a piece of
+-- data, using the default layout for the HTML output
+-- ('defaultLayout').
+--
+-- /Since: 0.3.0/
+defaultLayoutJson :: (Yesod master, J.ToJSON a)
+                  => GWidget sub master ()  -- ^ HTML
+                  -> a                      -- ^ JSON
                   -> GHandler sub master RepHtmlJson
 defaultLayoutJson w json = do
     RepHtml html' <- defaultLayout w
-    return $ RepHtmlJson html' $ toContent json
-
--- | Wraps the 'Content' generated by 'jsonToContent' in a 'RepJson'.
-jsonToRepJson :: J.Value -> GHandler sub master RepJson
-jsonToRepJson = return . RepJson . toContent
+    return $ RepHtmlJson html' $ toContent (J.toJSON json)
 
--- | Parse the request body as a JSON value.
+-- | Wraps a data type in a 'RepJson'.  The data type must
+-- support conversion to JSON via 'J.ToJSON'.
 --
--- /Since: 0.2.3/
-parseJsonBody :: GHandler sub master J.Value
-parseJsonBody = lift $ iterParser J.json'
+-- /Since: 0.3.0/
+jsonToRepJson :: J.ToJSON a => a -> GHandler sub master RepJson
+jsonToRepJson = return . RepJson . toContent . J.toJSON
 
+-- | Parse the request body to a data type as a JSON value.  The
+-- data type must support conversion from JSON via 'J.FromJSON'.
+-- If you want the raw JSON value, just ask for a @'J.Result'
+-- 'J.Value'@.
+--
+-- /Since: 0.3.0/
+parseJsonBody :: J.FromJSON a => GHandler sub master (J.Result a)
+parseJsonBody = do
+    req <- waiRequest
+    fmap J.fromJSON $ lift $ requestBody req $$ sinkParser J.json'
 
-type Json = J.Value
+-- | Same as 'parseJsonBody', but return an invalid args response on a parse
+-- error.
+parseJsonBody_ :: J.FromJSON a => GHandler sub master a
+parseJsonBody_ = do
+    ra <- parseJsonBody
+    case ra of
+        J.Error s -> invalidArgs [pack s]
+        J.Success a -> return a
 
-jsonScalar :: String -> Json
-jsonScalar = J.String . pack
+instance ToJavascript J.Value where
+    toJavascript = fromLazyText . decodeUtf8 . JE.encode
 
-jsonList :: [Json] -> Json
-jsonList = J.Array . V.fromList
+-- | Convert a list of pairs to an 'J.Object'.
+object :: J.ToJSON a => [(Text, a)] -> J.Value
+object = J.object . map (second J.toJSON)
 
-jsonMap :: [(String, Json)] -> Json
-jsonMap = J.Object . fromList . map (first pack)
+-- | Convert a list of values to an 'J.Array'.
+array :: J.ToJSON a => [a] -> J.Value
+array = J.Array . V.fromList . map J.toJSON
 
-instance ToJavascript J.Value where
-    toJavascript = fromLazyText . decodeUtf8 . JE.encode
+-- | jsonOrRedirect simplifies the scenario where a POST handler sends a different
+-- response based on Accept headers:
+--
+--     1. 200 with JSON data if the client prefers application/json (e.g. AJAX).
+--
+--     2. 3xx otherwise, following the PRG pattern.
+jsonOrRedirect :: (Yesod master, J.ToJSON a)
+               => Route master -- ^ Redirect target
+               -> a            -- ^ Data to send via JSON
+               -> GHandler sub master RepJson
+jsonOrRedirect r j = do
+    q <- acceptsJson
+    if q then jsonToRepJson (J.toJSON j)
+         else redirect r
+  where
+    acceptsJson = maybe False ((== "application/json") . B8.takeWhile (/= ';'))
+                . join
+                . fmap (headMay . parseHttpAccept)
+                . lookup "Accept" . requestHeaders
+                <$> waiRequest
diff --git a/yesod-json.cabal b/yesod-json.cabal
--- a/yesod-json.cabal
+++ b/yesod-json.cabal
@@ -1,5 +1,5 @@
 name:            yesod-json
-version:         0.2.3
+version:         0.3.1
 license:         BSD3
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
@@ -14,19 +14,24 @@
 
 library
     build-depends:   base                  >= 4        && < 5
-                   , yesod-core            >= 0.9      && < 0.10
-                   , aeson                 >= 0.3
-                   , text                  >= 0.8      && < 0.12
-                   , shakespeare-js        >= 0.10     && < 0.11
+                   , yesod-core            >= 0.10.1   && < 0.11
+                   , yesod-routes                         < 0.1
+                   , aeson                 >= 0.5
+                   , text                  >= 0.8      && < 1.0
+                   , shakespeare-js        >= 0.11     && < 0.12
                    , vector                >= 0.9
-                   , containers            >= 0.2      && < 0.5
-                   , unordered-containers
+                   , containers            >= 0.2
                    , blaze-builder
-                   , attoparsec-enumerator >= 0.3      && < 0.4
+                   , attoparsec-conduit    >= 0.2      && < 0.3
+                   , conduit               >= 0.2      && < 0.3
                    , transformers          >= 0.2.2    && < 0.3
+                   , wai                   >= 1.1      && < 1.2
+                   , wai-extra             >= 1.1      && < 1.2
+                   , bytestring            >= 0.9      && < 0.10
+                   , safe                  >= 0.2      && < 0.4
     exposed-modules: Yesod.Json
     ghc-options:     -Wall
 
 source-repository head
   type:     git
-  location: git://github.com/yesodweb/yesod.git
+  location: https://github.com/yesodweb/yesod
