packages feed

aeson 0.6.0.2 → 0.6.1.0

raw patch · 14 files changed

+504/−107 lines, 14 filesdep ~hashabledep ~timePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: hashable, time

API changes (from Hackage documentation)

+ Data.Aeson: eitherDecode :: FromJSON a => ByteString -> Either String a
+ Data.Aeson: eitherDecode' :: FromJSON a => ByteString -> Either String a
+ Data.Aeson: withArray :: String -> (Array -> Parser a) -> Value -> Parser a
+ Data.Aeson: withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
+ Data.Aeson: withNumber :: String -> (Number -> Parser a) -> Value -> Parser a
+ Data.Aeson: withObject :: String -> (Object -> Parser a) -> Value -> Parser a
+ Data.Aeson: withText :: String -> (Text -> Parser a) -> Value -> Parser a
+ Data.Aeson.Types: withArray :: String -> (Array -> Parser a) -> Value -> Parser a
+ Data.Aeson.Types: withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
+ Data.Aeson.Types: withNumber :: String -> (Number -> Parser a) -> Value -> Parser a
+ Data.Aeson.Types: withObject :: String -> (Object -> Parser a) -> Value -> Parser a
+ Data.Aeson.Types: withText :: String -> (Text -> Parser a) -> Value -> Parser a

Files

Data/Aeson.hs view
@@ -1,6 +1,6 @@ -- | -- Module:      Data.Aeson--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>@@ -13,9 +13,30 @@  module Data.Aeson     (+    -- * How to use this library+    -- $use++    -- ** Working with the AST+    -- $ast++    -- ** Decoding to a Haskell value+    -- $haskell++    -- ** Decoding a mixed-type object+    -- $mixed++    -- ** Automatically decoding data types+    -- $typeable++    -- ** Pitfalls+    -- $pitfalls+     -- * Encoding and decoding+    -- $encoding_and_decoding       decode     , decode'+    , eitherDecode+    , eitherDecode'     , encode     -- * Core JSON types     , Value(..)@@ -28,6 +49,12 @@     , Result(..)     , fromJSON     , ToJSON(..)+    -- * Inspecting @'Value's@+    , withObject+    , withText+    , withArray+    , withNumber+    , withBool     -- * Constructors and accessors     , (.=)     , (.:)@@ -40,7 +67,7 @@     ) where  import Data.Aeson.Encode (encode)-import Data.Aeson.Parser.Internal (decodeWith, json, json')+import Data.Aeson.Parser.Internal (decodeWith, eitherDecodeWith, json, json') import Data.Aeson.Types import qualified Data.ByteString.Lazy as L @@ -63,3 +90,229 @@ decode' :: (FromJSON a) => L.ByteString -> Maybe a decode' = decodeWith json' fromJSON {-# INLINE decode' #-}++-- | Like 'decode' but returns an error message when decoding fails.+eitherDecode :: (FromJSON a) => L.ByteString -> Either String a+eitherDecode = eitherDecodeWith json fromJSON+{-# INLINE eitherDecode #-}++-- | Like 'decode'' but returns an error message when decoding fails.+eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a+eitherDecode' = eitherDecodeWith json' fromJSON+{-# INLINE eitherDecode' #-}++-- $use+--+-- This section contains basic information on the different ways to+-- decode data using this library. These range from simple but+-- inflexible, to complex but flexible.+--+-- The most common way to use the library is to define a data type,+-- corresponding to some JSON data you want to work with, and then+-- write either a 'FromJSON' instance, a to 'ToJSON' instance, or both+-- for that type. For example, given this JSON data:+--+-- > { "name": "Joe", "age": 12 }+--+-- we create a matching data type:+--+-- > data Person = Person+-- >     { name :: Text+-- >     , age  :: Int+-- >     } deriving Show-+--+-- To decode data, we need to define a 'FromJSON' instance:+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > instance FromJSON Coord where+-- >     parseJSON (Object v) = Person <$>+-- >                            v .: "name" <*>+-- >                            v .: "age"+-- >     -- A non-Object value is of the wrong type, so fail.+-- >     parseJSON _          = mzero+--+-- We can now parse the JSON data like so:+--+-- > >>> decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person+-- > Just (Person {name = "Joe", age = 12})+--+-- To encode data, we need to define a 'ToJSON' instance:+--+-- > instance ToJSON Person where+-- >     toJSON (Person name age) = object ["name" .= name, "age" .= age]+--+-- We can now encode a value like so:+--+-- > >>> encode (Person {name = "Joe", age = 12})+-- > "{\"name\":\"Joe\",\"age\":12}"+--+-- There are predefined 'FromJSON' and 'ToJSON' instances for many+-- types. Here's an example using lists and 'Int's:+--+-- > >>> decode "[1,2,3]" :: Maybe [Int]+-- > Just [1,2,3]+--+-- And here's an example using the 'Data.Map.Map' type to get a map of+-- 'Int's.+--+-- > >>> decode "{\"foo\":1,\"bar\":2}" :: Maybe (Map String Int)+-- > Just (fromList [("bar",2),("foo",1)])++-- While the notes below focus on decoding, you can apply almost the+-- same techniques to /encoding/ data. (The main difference is that+-- encoding always succeeds, but decoding has to handle the+-- possibility of failure, where an input doesn't match our+-- expectations.)+--+-- See the documentation of 'FromJSON' and 'ToJSON' for some examples+-- of how you can automatically derive instances in some+-- circumstances.++-- $ast+--+-- Sometimes you want to work with JSON data directly, without first+-- converting it to a custom data type. This can be useful if you want+-- to e.g. convert JSON data to YAML data, without knowing what the+-- contents of the original JSON data was. The 'Value' type, which is+-- an instance of 'FromJSON', is used to represent an arbitrary JSON+-- AST (abstract syntax tree). Example usage:+--+-- > >>> decode "{\"foo\": 123}" :: Maybe Value+-- > Just (Object (fromList [("foo",Number 123)]))+--+-- > >>> decode "{\"foo\": [\"abc\",\"def\"]}" :: Maybe Value+-- > Just (Object (fromList [("foo",Array (fromList [String "abc",String "def"]))]))+--+-- Once you have a 'Value' you can write functions to traverse it and+-- make arbitrary transformations.++-- $haskell+--+-- Any instance of 'FromJSON' can be specified (but see the+-- \"Pitfalls\" section here&#8212;"Data.Aeson#pitfalls"):+--+-- > λ> decode "[1,2,3]" :: Maybe [Int]+-- > Just [1,2,3]+--+-- Alternatively, there are instances for standard data types, so you+-- can use them directly. For example, use the 'Data.Map.Map' type to+-- get a map of 'Int's.+--+-- > λ> :m + Data.Map+-- > λ> decode "{\"foo\":1,\"bar\":2}" :: Maybe (Map String Int)+-- > Just (fromList [("bar",2),("foo",1)])++-- $mixed+--+-- The above approach with maps of course will not work for mixed-type+-- objects that don't follow a strict schema, but there are a couple+-- of approaches available for these.+--+-- The 'Object' type contains JSON objects:+--+-- > λ> decode "{\"name\":\"Dave\",\"age\":2}" :: Maybe Object+-- > Just (fromList) [("name",String "Dave"),("age",Number 2)]+--+-- You can extract values from it with a parser using 'parse',+-- 'parseEither' or, in this example, 'parseMaybe':+--+-- > λ> do result <- decode "{\"name\":\"Dave\",\"age\":2}"+-- >       flip parseMaybe result $ \obj -> do+-- >         age <- obj .: "age"+-- >         name <- obj .: "name"+-- >         return (name ++ ": " ++ show (age*2))+-- >+-- > Just "Dave: 4"+--+-- Considering that any type that implements 'FromJSON' can be used+-- here, this is quite a powerful way to parse JSON. See the+-- documentation in 'FromJSON' for how to implement this class for+-- your own data types.+--+-- The downside is that you have to write the parser yourself; the+-- upside is that you have complete control over the way the JSON is+-- parsed.++-- $typeable+--+-- If you don't want fine control and would prefer the JSON be parsed+-- to your own data types automatically according to some reasonably+-- sensible isomorphic implementation, you can use the generic parser+-- based on 'Data.Typeable.Typeable' and 'Data.Data.Data'. Switch to+-- the 'Data.Aeson.Generic' module, and you can do the following:+--+-- > λ> decode "[1]" :: Maybe [Int]+-- > Just [1]+-- > λ> :m + Data.Typeable Data.Data+-- > λ> :set -XDeriveDataTypeable+-- > λ> data Person = Person { personName :: String, personAge :: Int } deriving (Data,Typeable,Show)+-- > λ> encode Person { personName = "Chris", personAge = 123 }+-- > "{\"personAge\":123,\"personName\":\"Chris\"}"+-- > λ> decode "{\"personAge\":123,\"personName\":\"Chris\"}" :: Maybe Person+-- > Just (Person {+-- > personName = "Chris", personAge = 123+-- > })+--+-- Be aware that the encoding may not always be what you'd naively+-- expect:+--+-- > λ> data Foo = Foo Int Int deriving (Data,Typeable,Show)+-- > λ> encode (Foo 1 2)+-- > "[1,2]"+--+-- With this approach, it's best to treat the+-- 'Data.Aeson.Generic.decode' and 'Data.Aeson.Generic.encode'+-- functions as an isomorphism, and not to rely upon (or care about)+-- the specific intermediate representation.++-- $pitfalls+-- #pitfalls#+--+-- Note that the JSON standard only allows arrays or objects of things+-- at the top-level. Since this library follows the standard, calling+-- 'decode' on an unsupported result type will typecheck, but will+-- always \"fail\":+--+-- > >>> decode "1" :: Maybe Int+-- > Nothing+-- > >>> decode "1" :: Maybe String+-- > Nothing+--+-- So stick to objects (e.g. maps in Haskell) or arrays (lists or+-- vectors in Haskell):+--+-- > >>> decode "[1,2,3]" :: Maybe [Int]+-- > Just [1,2,3]+--+-- When encoding to JSON you can encode anything that's an instance of+-- 'ToJSON', and this may include simple types. So beware that this+-- aspect of the API is not isomorphic. You can round-trip arrays and+-- maps, but not simple values:+--+-- > >>> encode [1,2,3]+-- > "[1,2,3]"+-- > >>> decode (encode [1]) :: Maybe [Int]+-- > Just [1]+-- > >>> encode 1+-- > "1"+-- > >>> decode (encode (1 :: Int)) :: Maybe Int+-- > Nothing+--+-- Alternatively, see 'Data.Aeson.Parser.value' to parse non-top-level+-- JSON values.++-- $encoding_and_decoding+--+-- Encoding and decoding are each two-step processes.+--+-- * To encode a value, it is first converted to an abstract syntax+--   tree (AST), using 'ToJSON'. This generic representation is then+--   encoded as bytes.+--+-- * When decoding a value, the process is reversed: the bytes are+--   converted to an AST, then the 'FromJSON' class is used to convert+--   to the desired type.+--+-- For convenience, the 'encode' and+-- 'decode' functions combine both steps for convenience.
Data/Aeson/Encode.hs view
@@ -2,7 +2,8 @@  -- | -- Module:      Data.Aeson.Encode--- Copyright:   (c) 2011 MailRank, Inc.+-- Copyright:   (c) 2012 Bryan O'Sullivan+--              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com> -- Stability:   experimental@@ -62,15 +63,25 @@ string s = {-# SCC "string" #-} singleton '"' <> quote s <> singleton '"'   where     quote q = case T.uncons t of-                Nothing     -> fromText h+                Nothing      -> fromText h                 Just (!c,t') -> fromText h <> escape c <> quote t'         where (h,t) = {-# SCC "break" #-} T.break isEscape q-    isEscape c = c == '\"' || c == '\\' || c < '\x20'+    isEscape c = c == '\"' ||+                 c == '\\' ||+                 c == '<'  ||+                 c == '>'  ||+                 c < '\x20'     escape '\"' = "\\\""     escape '\\' = "\\\\"     escape '\n' = "\\n"     escape '\r' = "\\r"     escape '\t' = "\\t"++    -- The following prevents untrusted JSON strings containing </script> or -->+    -- from causing an XSS vulnerability:+    escape '<'  = "\\u003c"+    escape '>'  = "\\u003e"+     escape c         | c < '\x20' = fromString $ "\\u" ++ replicate (4 - length h) '0' ++ h         | otherwise  = singleton c
Data/Aeson/Functions.hs view
@@ -1,6 +1,6 @@ -- | -- Module:      Data.Aeson.Functions--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
Data/Aeson/Generic.hs view
@@ -2,7 +2,7 @@  -- | -- Module:      Data.Aeson.Generic--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. --              (c) 2008, 2009 Lennart Augustsson -- License:     BSD3@@ -290,6 +290,7 @@         decodeArgs c0 = go (numConstrArgs (resType generic) c0) c0                            (constrFields c0)          where+          go 0 c _        Null       = construct c []           go 1 c []       jd         = construct c [jd] -- unary constructor           go _ c []       (Array js) = construct c (V.toList js) -- no field names           -- FIXME? We could allow reading an array into a constructor
Data/Aeson/Parser.hs view
@@ -2,7 +2,8 @@  -- | -- Module:      Data.Aeson.Parser--- Copyright:   (c) 2011 MailRank, Inc.+-- Copyright:   (c) 2012 Bryan O'Sullivan+--              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com> -- Stability:   experimental
Data/Aeson/Parser/Internal.hs view
@@ -2,7 +2,7 @@  -- | -- Module:      Data.Aeson.Parser.Internal--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>@@ -23,6 +23,7 @@     , value'     -- * Helpers     , decodeWith+    , eitherDecodeWith     ) where  import Blaze.ByteString.Builder (fromByteString, toByteString)@@ -237,6 +238,15 @@                       _         -> Nothing       _          -> Nothing {-# INLINE decodeWith #-}++eitherDecodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Either String a+eitherDecodeWith p to s =+    case L.parse p s of+      L.Done _ v -> case to v of+                      Success a -> Right a+                      Error msg -> Left msg+      L.Fail _ _ msg -> Left msg+{-# INLINE eitherDecodeWith #-}  -- $lazy --
Data/Aeson/TH.hs view
@@ -2,7 +2,7 @@  {-| Module:      Data.Aeson.TH-Copyright:   (c) 2011 Bryan O'Sullivan+Copyright:   (c) 2011, 2012 Bryan O'Sullivan              (c) 2011 MailRank, Inc. License:     Apache Stability:   experimental@@ -46,7 +46,7 @@  instance 'ToJSON' a => 'ToJSON' (D a) where     'toJSON' =-      \value ->+      \\value ->         case value of           Nullary ->               'object' [T.pack \"Nullary\" .= 'toJSON' ([] :: [()])]@@ -73,7 +73,7 @@ @ instance 'FromJSON' a => 'FromJSON' (D a) where     'parseJSON' =-      \value ->+      \\value ->         case value of           'Object' obj ->             case H.toList obj of@@ -234,7 +234,7 @@ -- @ -- instance 'ToJSON' Foo where --      'toJSON' =---          \value -> case value of+--          \\value -> case value of --                      Foo arg1 arg2 -> 'Array' $ 'V.create' $ do --                        mv <- 'VM.unsafeNew' 2 --                        'VM.unsafeWrite' mv 0 ('toJSON' arg1)@@ -281,7 +281,7 @@ -- This will splice in the following code: -- -- @--- \value -> case value of Foo arg1 -> 'toJSON' arg1+-- \\value -> case value of Foo arg1 -> 'toJSON' arg1 -- @ mkToJSON :: (String -> String) -- ^ Function to change field names.          -> Name -- ^ Name of the type to encode.@@ -406,7 +406,7 @@ -- @ -- instance 'FromJSON' Foo where --     'parseJSON' =---         \value -> case value of+--         \\value -> case value of --                     'Array' arr -> --                       if (V.length arr == 2) --                       then Foo \<$\> 'parseJSON' (arr `V.unsafeIndex` 0)
Data/Aeson/Types.hs view
@@ -2,7 +2,7 @@  -- | -- Module:      Data.Aeson.Types--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>@@ -32,6 +32,14 @@     , parseEither     , parseMaybe     , ToJSON(..)++    -- * Inspecting @'Value's@+    , withObject+    , withText+    , withArray+    , withNumber+    , withBool+     -- * Constructors and accessors     , (.=)     , (.:)
Data/Aeson/Types/Class.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, FlexibleInstances,     GeneralizedNewtypeDeriving, IncoherentInstances, OverlappingInstances,     OverloadedStrings, UndecidableInstances, ViewPatterns #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}  #ifdef GENERICS {-# LANGUAGE DefaultSignatures #-}@@ -8,7 +9,7 @@  -- | -- Module:      Data.Aeson.Types.Class--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>@@ -30,6 +31,14 @@ #endif     -- * Types     , DotNetTime(..)++      -- * Inspecting @'Value's@+    , withObject+    , withText+    , withArray+    , withNumber+    , withBool+     -- * Functions     , fromJSON     , (.:)@@ -39,18 +48,19 @@     , typeMismatch     ) where -import Control.Applicative ((<$>), (<*>), pure)+import Control.Applicative ((<$>), (<*>), (<|>), pure, empty) import Data.Aeson.Functions import Data.Aeson.Types.Internal import Data.Attoparsec.Char8 (Number(..))+import Data.Fixed import Data.Hashable (Hashable(..)) import Data.Int (Int8, Int16, Int32, Int64) import Data.Maybe (fromMaybe)-import Data.Monoid (Dual(..), First(..), Last(..))+import Data.Monoid (Dual(..), First(..), Last(..), mappend) import Data.Ratio (Ratio) import Data.Text (Text, pack, unpack) import Data.Text.Encoding (encodeUtf8)-import Data.Time.Clock (UTCTime)+import Data.Time (UTCTime, ZonedTime(..), TimeZone(..)) import Data.Time.Format (FormatTime, formatTime, parseTime) import Data.Traversable (traverse) import Data.Typeable (Typeable)@@ -111,8 +121,8 @@ -- type which is an instance of 'Data'. -- -- * If your compiler has support for the @DeriveGeneric@ and--- @DefaultSignatures@ language extensions, @toJSON@ will have a default generic--- implementation.+-- @DefaultSignatures@ language extensions (GHC 7.2 and newer),+-- @toJSON@ will have a default generic implementation. -- -- To use the latter option, simply add a @deriving 'Generic'@ clause to your -- datatype and declare a @ToJSON@ instance for your datatype without giving a@@ -228,8 +238,7 @@     {-# INLINE toJSON #-}  instance FromJSON Bool where-    parseJSON (Bool b) = pure b-    parseJSON v        = typeMismatch "Bool" v+    parseJSON = withBool "Bool" pure     {-# INLINE parseJSON #-}  instance ToJSON () where@@ -237,8 +246,10 @@     {-# INLINE toJSON #-}  instance FromJSON () where-    parseJSON (Array v) | V.null v = pure ()-    parseJSON v        = typeMismatch "()" v+    parseJSON = withArray "()" $ \v ->+                  if V.null v+                    then pure ()+                    else fail "Expected an empty array"     {-# INLINE parseJSON #-}  instance ToJSON [Char] where@@ -246,8 +257,7 @@     {-# INLINE toJSON #-}  instance FromJSON [Char] where-    parseJSON (String t) = pure (T.unpack t)-    parseJSON v          = typeMismatch "String" v+    parseJSON = withText "String" $ pure . T.unpack     {-# INLINE parseJSON #-}  instance ToJSON Char where@@ -255,9 +265,10 @@     {-# INLINE toJSON #-}  instance FromJSON Char where-    parseJSON (String t)-        | T.compareLength t 1 == EQ = pure (T.head t)-    parseJSON v          = typeMismatch "Char" v+    parseJSON = withText "Char" $ \t ->+                  if T.compareLength t 1 == EQ+                    then pure $ T.head t+                    else fail "Expected a string of length 1"     {-# INLINE parseJSON #-}  instance ToJSON Double where@@ -299,10 +310,21 @@     {-# INLINE toJSON #-}  instance FromJSON (Ratio Integer) where+    parseJSON = withNumber "Ration Integer" $ \n ->+                  pure $ case n of+                           D d -> toRational d+                           I i -> fromIntegral i+    {-# INLINE parseJSON #-}++instance HasResolution a => ToJSON (Fixed a) where+    toJSON = Number . realToFrac+    {-# INLINE toJSON #-}++instance HasResolution a => FromJSON (Fixed a) where     parseJSON (Number n) = pure $ case n of-                                    D d -> toRational d+                                    D d -> realToFrac d                                     I i -> fromIntegral i-    parseJSON v          = typeMismatch "Ratio Integer" v+    parseJSON v          = typeMismatch "Fixed" v     {-# INLINE parseJSON #-}  instance ToJSON Int where@@ -314,8 +336,7 @@     {-# INLINE parseJSON #-}  parseIntegral :: Integral a => Value -> Parser a-parseIntegral (Number n) = pure (floor n)-parseIntegral v          = typeMismatch "Integral" v+parseIntegral = withNumber "Integral" $ pure . floor {-# INLINE parseIntegral #-}  instance ToJSON Integer where@@ -403,8 +424,7 @@     {-# INLINE toJSON #-}  instance FromJSON Text where-    parseJSON (String t) = pure t-    parseJSON v          = typeMismatch "Text" v+    parseJSON = withText "Text" pure     {-# INLINE parseJSON #-}  instance ToJSON LT.Text where@@ -412,8 +432,7 @@     {-# INLINE toJSON #-}  instance FromJSON LT.Text where-    parseJSON (String t) = pure (LT.fromStrict t)-    parseJSON v          = typeMismatch "Lazy Text" v+    parseJSON = withText "Lazy Text" $ pure . LT.fromStrict     {-# INLINE parseJSON #-}  instance ToJSON B.ByteString where@@ -421,8 +440,7 @@     {-# INLINE toJSON #-}  instance FromJSON B.ByteString where-    parseJSON (String t) = pure . encodeUtf8 $ t-    parseJSON v          = typeMismatch "ByteString" v+    parseJSON = withText "ByteString" $ pure . encodeUtf8     {-# INLINE parseJSON #-}  instance ToJSON LB.ByteString where@@ -430,8 +448,7 @@     {-# INLINE toJSON #-}  instance FromJSON LB.ByteString where-    parseJSON (String t) = pure . lazy $ t-    parseJSON v          = typeMismatch "Lazy ByteString" v+    parseJSON = withText "Lazy ByteString" $ pure . lazy     {-# INLINE parseJSON #-}  instance (ToJSON a) => ToJSON [a] where@@ -439,8 +456,7 @@     {-# INLINE toJSON #-}  instance (FromJSON a) => FromJSON [a] where-    parseJSON (Array a) = mapM parseJSON (V.toList a)-    parseJSON v         = typeMismatch "[a]" v+    parseJSON = withArray "[a]" $ mapM parseJSON . V.toList     {-# INLINE parseJSON #-}  instance (ToJSON a) => ToJSON (Vector a) where@@ -448,8 +464,7 @@     {-# INLINE toJSON #-}  instance (FromJSON a) => FromJSON (Vector a) where-    parseJSON (Array a) = V.mapM parseJSON a-    parseJSON v         = typeMismatch "Vector a" v+    parseJSON = withArray "Vector a" $ V.mapM parseJSON     {-# INLINE parseJSON #-}  vectorToJSON :: (VG.Vector v a, ToJSON a) => v a -> Value@@ -457,8 +472,7 @@ {-# INLINE vectorToJSON #-}  vectorParseJSON :: (FromJSON a, VG.Vector w a) => String -> Value -> Parser (w a)-vectorParseJSON _ (Array a) = V.convert <$> V.mapM parseJSON a-vectorParseJSON s v         = typeMismatch s v+vectorParseJSON s = withArray s $ fmap V.convert . V.mapM parseJSON {-# INLINE vectorParseJSON #-}  instance (Storable a, ToJSON a) => ToJSON (VS.Vector a) where@@ -516,8 +530,8 @@     {-# INLINE toJSON #-}  instance (FromJSON v) => FromJSON (M.Map Text v) where-    parseJSON (Object o) = H.foldrWithKey M.insert M.empty <$> traverse parseJSON o-    parseJSON v          = typeMismatch "Map Text a" v+    parseJSON = withObject "Map Text a" $+                  fmap (H.foldrWithKey M.insert M.empty) . traverse parseJSON  instance (ToJSON v) => ToJSON (M.Map LT.Text v) where     toJSON = Object . mapHashKeyVal LT.toStrict toJSON@@ -548,8 +562,7 @@     {-# INLINE toJSON #-}  instance (FromJSON v) => FromJSON (H.HashMap Text v) where-    parseJSON (Object o) = traverse parseJSON o-    parseJSON v          = typeMismatch "HashMap Text a" v+    parseJSON = withObject "HashMap Text a" $ traverse parseJSON  instance (ToJSON v) => ToJSON (H.HashMap LT.Text v) where     toJSON = Object . mapKeyVal LT.toStrict toJSON@@ -602,26 +615,53 @@     {-# INLINE toJSON #-}  instance FromJSON DotNetTime where-    parseJSON (String t) =-        case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of-          Just d -> pure (DotNetTime d)-          _      -> fail "could not parse .NET time"-      where (s,m) = T.splitAt (T.length t - 5) t+    parseJSON = withText "DotNetTime" $ \t ->+        let (s,m) = T.splitAt (T.length t - 5) t             t'    = T.concat [s,".",m]-    parseJSON v   = typeMismatch "DotNetTime" v+        in case parseTime defaultTimeLocale "/Date(%s%Q)/" (unpack t') of+             Just d -> pure (DotNetTime d)+             _      -> fail "could not parse .NET time"     {-# INLINE parseJSON #-} +instance ToJSON ZonedTime where+    toJSON t = String $ pack $ formatTime defaultTimeLocale format t+      where+        format = "%FT%T" ++ milliseconds ++ tzFormat+        milliseconds = take 4 $ formatTime defaultTimeLocale "%Q" t+        tzFormat+          | 0 == timeZoneMinutes (zonedTimeZone t) = "Z"+          | otherwise = "%z"++instance FromJSON ZonedTime where+    parseJSON (String t) =+      tryFormats alternateFormats+      <|> fail "could not parse ECMA-262 ISO-8601 date"+      where+        tryFormat f =+          case parseTime defaultTimeLocale f (unpack t) of+            Just d -> pure d+            Nothing -> empty+        tryFormats = foldr1 (<|>) . map tryFormat+        alternateFormats =+          distributeList ["%Y", "%Y-%m", "%F"]+                         ["T%R", "T%T", "T%T%Q", "T%T%QZ", "T%T%Q%z"]++        distributeList xs ys =+          foldr (\x acc -> acc ++ distribute x ys) [] xs+        distribute x = map (mappend x)++    parseJSON v = typeMismatch "ZonedTime" v+ instance ToJSON UTCTime where     toJSON t = String (pack (take 23 str ++ "Z"))       where str = formatTime defaultTimeLocale "%FT%T%Q" t     {-# INLINE toJSON #-}  instance FromJSON UTCTime where-    parseJSON (String t) =+    parseJSON = withText "UTCTime" $ \t ->         case parseTime defaultTimeLocale "%FT%T%QZ" (unpack t) of           Just d -> pure d           _      -> fail "could not parse ISO-8601 date"-    parseJSON v   = typeMismatch "UTCTime" v     {-# INLINE parseJSON #-}  instance (ToJSON a, ToJSON b) => ToJSON (a,b) where@@ -633,14 +673,13 @@     {-# INLINE toJSON #-}  instance (FromJSON a, FromJSON b) => FromJSON (a,b) where-    parseJSON (Array ab)-        | n == 2    = (,) <$> parseJSON (V.unsafeIndex ab 0)-                          <*> parseJSON (V.unsafeIndex ab 1)-        | otherwise = fail $ "cannot unpack array of length " ++-                        show n ++ " into a pair"-          where-            n = V.length ab-    parseJSON v = typeMismatch "(a,b)" v+    parseJSON = withArray "(a,b)" $ \ab ->+        let n = V.length ab+        in if n == 2+             then (,) <$> parseJSON (V.unsafeIndex ab 0)+                      <*> parseJSON (V.unsafeIndex ab 1)+             else fail $ "cannot unpack array of length " +++                         show n ++ " into a pair"     {-# INLINE parseJSON #-}  instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON (a,b,c) where@@ -653,15 +692,14 @@     {-# INLINE toJSON #-}  instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON (a,b,c) where-    parseJSON (Array abc)-        | n == 3    = (,,) <$> parseJSON (V.unsafeIndex abc 0)-                           <*> parseJSON (V.unsafeIndex abc 1)-                           <*> parseJSON (V.unsafeIndex abc 2)-        | otherwise = fail $ "cannot unpack array of length " ++-                        show n ++ " into a 3-tuple"-          where-            n = V.length abc-    parseJSON v = typeMismatch "(a,b,c)" v+    parseJSON = withArray "(a,b,c)" $ \abc ->+        let n = V.length abc+        in if n == 3+             then (,,) <$> parseJSON (V.unsafeIndex abc 0)+                       <*> parseJSON (V.unsafeIndex abc 1)+                       <*> parseJSON (V.unsafeIndex abc 2)+             else fail $ "cannot unpack array of length " +++                          show n ++ " into a 3-tuple"     {-# INLINE parseJSON #-}  instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON (a,b,c,d) where@@ -675,16 +713,15 @@     {-# INLINE toJSON #-}  instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON (a,b,c,d) where-    parseJSON (Array abcd)-        | n == 4    = (,,,) <$> parseJSON (V.unsafeIndex abcd 0)-                            <*> parseJSON (V.unsafeIndex abcd 1)-                            <*> parseJSON (V.unsafeIndex abcd 2)-                            <*> parseJSON (V.unsafeIndex abcd 3)-        | otherwise = fail $ "cannot unpack array of length " ++-                        show n ++ " into a 4-tuple"-          where-            n = V.length abcd-    parseJSON v = typeMismatch "(a,b,c,d)" v+    parseJSON = withArray "(a,b,c,d)" $ \abcd ->+        let n = V.length abcd+        in if n == 4+             then (,,,) <$> parseJSON (V.unsafeIndex abcd 0)+                        <*> parseJSON (V.unsafeIndex abcd 1)+                        <*> parseJSON (V.unsafeIndex abcd 2)+                        <*> parseJSON (V.unsafeIndex abcd 3)+             else fail $ "cannot unpack array of length " +++                         show n ++ " into a 4-tuple"     {-# INLINE parseJSON #-}  instance ToJSON a => ToJSON (Dual a) where@@ -711,6 +748,41 @@     parseJSON = fmap Last . parseJSON     {-# INLINE parseJSON #-} +-- | @withObject expected f value@ applies @f@ to the 'Object' when @value@ is an @Object@+--   and fails using @'typeMismatch' expected@ otherwise.+withObject :: String -> (Object -> Parser a) -> Value -> Parser a+withObject _        f (Object obj) = f obj+withObject expected _ v            = typeMismatch expected v+{-# INLINE withObject #-}++-- | @withObject expected f value@ applies @f@ to the 'Text' when @value@ is a @String@+--   and fails using @'typeMismatch' expected@ otherwise.+withText :: String -> (Text -> Parser a) -> Value -> Parser a+withText _        f (String txt) = f txt+withText expected _ v            = typeMismatch expected v+{-# INLINE withText #-}++-- | @withObject expected f value@ applies @f@ to the 'Array' when @value@ is an @Array@+--   and fails using @'typeMismatch' expected@ otherwise.+withArray :: String -> (Array -> Parser a) -> Value -> Parser a+withArray _        f (Array arr) = f arr+withArray expected _ v           = typeMismatch expected v+{-# INLINE withArray #-}++-- | @withObject expected f value@ applies @f@ to the 'Number' when @value@ is a @Number@+--   and fails using @'typeMismatch' expected@ otherwise.+withNumber :: String -> (Number -> Parser a) -> Value -> Parser a+withNumber _        f (Number num) = f num+withNumber expected _ v            = typeMismatch expected v+{-# INLINE withNumber #-}++-- | @withObject expected f value@ applies @f@ to the 'Bool' when @value@ is a @Bool@+--   and fails using @'typeMismatch' expected@ otherwise.+withBool :: String -> (Bool -> Parser a) -> Value -> Parser a+withBool _        f (Bool arr) = f arr+withBool expected _ v          = typeMismatch expected v+{-# INLINE withBool #-}+ -- | Construct a 'Pair' from a key and a value. (.=) :: ToJSON a => Text -> a -> Pair name .= value = (name, toJSON value)@@ -754,7 +826,7 @@ -- from an object without affecting its validity and we know a default -- value to assign in that case.  If the key and value are mandatory, -- use '(.:)' instead.--- +-- -- Example usage: -- -- @ v1 <- o '.:?' \"opt_field_with_dfl\" .!= \"default_val\"
Data/Aeson/Types/Generic.hs view
@@ -6,7 +6,7 @@ -- | -- Module:      Data.Aeson.Types.Generic -- Copyright:   (c) 2012 Bryan O'Sullivan---              (c) 2011 Bas Van Dijk+--              (c) 2011, 2012 Bas Van Dijk --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
Data/Aeson/Types/Internal.hs view
@@ -2,7 +2,7 @@  -- | -- Module:      Data.Aeson.Types.Internal--- Copyright:   (c) 2011 Bryan O'Sullivan+-- Copyright:   (c) 2011, 2012 Bryan O'Sullivan --              (c) 2011 MailRank, Inc. -- License:     Apache -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>@@ -177,12 +177,16 @@     {-# INLINE fromString #-}  instance Hashable Value where-    hash (Object o) = H.foldl' hashWithSalt 0 o-    hash (Array a)  = V.foldl' hashWithSalt 1 a-    hash (String s) = 2 `hashWithSalt` s-    hash (Number n) = 3 `hashWithSalt` case n of I i -> hash i; D d -> hash d-    hash (Bool b)   = 4 `hashWithSalt` b-    hash Null       = 5+    hashWithSalt s (Object o)   = H.foldl' hashWithSalt+                                  (s `hashWithSalt` (0::Int)) o+    hashWithSalt s (Array a)    = V.foldl' hashWithSalt+                                  (s `hashWithSalt` (1::Int)) a+    hashWithSalt s (String str) = s `hashWithSalt` (2::Int) `hashWithSalt` str+    hashWithSalt s (Number n)   = 3 `hashWithSalt`+                                  case n of I i -> hashWithSalt s i+                                            D d -> hashWithSalt s d+    hashWithSalt s (Bool b)   = s `hashWithSalt` (4::Int) `hashWithSalt` b+    hashWithSalt s Null       = s `hashWithSalt` (5::Int)  -- | The empty array. emptyArray :: Value
aeson.cabal view
@@ -1,9 +1,9 @@ name:            aeson-version:         0.6.0.2+version:         0.6.1.0 license:         BSD3 license-file:    LICENSE category:        Text, Web, JSON-copyright:       (c) 2011 Bryan O'Sullivan+copyright:       (c) 2011, 2012 Bryan O'Sullivan                  (c) 2011 MailRank, Inc. author:          Bryan O'Sullivan <bos@serpentine.com> maintainer:      Bryan O'Sullivan <bos@serpentine.com>@@ -24,11 +24,6 @@     For release notes, see     <https://github.com/bos/aeson/blob/master/release-notes.markdown>     .-    /Note/: if you use GHCi or Template Haskell, please see the-    @README@ file for important details about building this package,-    and other packages that depend on it:-    <https://github.com/bos/aeson#readme>-    .     Parsing performance on a late 2010 MacBook Pro (2.66GHz Core i7),     for mostly-English tweets from Twitter's JSON search API:     .@@ -159,7 +154,8 @@     template-haskell,     test-framework,     test-framework-quickcheck2,-    text+    text,+    time  source-repository head   type:     git
release-notes.markdown view
@@ -1,3 +1,23 @@+# 0.6 series++Much improved documentation.++Angle brackets are now escaped in JSON strings, to help avoid XSS+attacks.++Fixed up handling of nullary constructors when using generic encoding.++Added ToJSON/FromJSON instances for:++* The `Fixed` class++* ISO-8601 dates: `UTCTime`, `ZonedTime`, and `TimeZone`++Added accessor functions for inspecting `Value`s.++Added `eitherDecode` function that returns an error message if+decoding fails.+ # 0.5 to 0.6  This release introduces a slightly obscure, but
tests/Properties.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE DeriveDataTypeable, OverloadedStrings, RecordWildCards,-    ScopedTypeVariables #-}+    ScopedTypeVariables, StandaloneDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-}  import Control.Monad@@ -12,12 +12,13 @@ import Data.Text (Text) import Test.Framework (Test, defaultMain, testGroup) import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Arbitrary(..))+import Test.QuickCheck (Arbitrary(..), choose, Gen) import qualified Data.Aeson.Generic as G import qualified Data.Attoparsec.Lazy as L import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.Text as T import qualified Data.Map as Map+import Data.Time (ZonedTime(..), LocalTime(..), TimeZone(..), hoursToTimeZone, Day(..), TimeOfDay(..))  encodeDouble :: Double -> Double -> Bool encodeDouble num denom@@ -61,6 +62,9 @@                 Error _ -> False                 Success x' -> x == x' +regress_gh72 :: [(String, Maybe String)] -> Bool+regress_gh72 ys = G.decode (G.encode m) == Just m+    where m = Map.fromList ys  data Foo = Foo {       fooInt :: Int@@ -100,6 +104,17 @@ instance Arbitrary Foo where     arbitrary = liftM4 Foo arbitrary arbitrary arbitrary arbitrary +instance Arbitrary LocalTime where+    arbitrary = return $ LocalTime (ModifiedJulianDay 1) (TimeOfDay 1 2 3)++instance Arbitrary TimeZone where+    arbitrary = do+      offset <- choose (0,2) :: Gen Int+      return $ hoursToTimeZone offset++instance Arbitrary ZonedTime where+    arbitrary = liftM2 ZonedTime arbitrary arbitrary+ data UFoo = UFoo {       _UFooInt :: Int     , uFooInt :: Int@@ -112,8 +127,13 @@ main :: IO () main = defaultMain tests +deriving instance Eq ZonedTime+ tests :: [Test] tests = [+  testGroup "regression" [+      testProperty "gh-72" regress_gh72+  ],   testGroup "encode" [       testProperty "encodeDouble" encodeDouble     , testProperty "encodeInteger" encodeInteger@@ -138,6 +158,7 @@     , testProperty "String" $ roundTripEq (""::String)     , testProperty "Text" $ roundTripEq T.empty     , testProperty "Foo" $ roundTripEq (undefined::Foo)+    , testProperty "ZonedTime" $ roundTripEq (undefined::ZonedTime)     ],   testGroup "toFromJSON" [       testProperty "Integer" (toFromJSON :: Integer -> Bool)