on master help sign in
Merge
commit7d1e708fc6b733027b056c3985584a8893b89005 authorBryan O'Sullivan <bos@serpentine.com> dateTue 29 Oct 2013, 23:13 (13 years ago) parent260a7e89795b87
8 files changed+72−52
.gitignore+2 Data/Aeson/Encode.hs+8−8 Data/Aeson/Parser/Internal.hs+2−2 Data/Aeson/Types.hs+1 Data/Aeson/Types/Instances.hs+48−29 Data/Aeson/Types/Internal.hs+6−8 aeson.cabal+2−1 tests/Properties.hs+3−4
.gitignore+2
@@ -6,3 +6,5 @@ dist
66benchmarks/AesonEncode
77
88tests/qc
9+
10+cabal.sandbox.config
Data/Aeson/Encode.hs+8−8
@@ -24,11 +24,10 @@ module Data.Aeson.Encode
2424 ) where
2525
2626import Data.Aeson.Types (ToJSON(..), Value(..))
27−import Data.Attoparsec.Number (Number(..))
2827import Data.Monoid (mappend)
28+import Data.Scientific (Scientific, coefficient, base10Exponent, scientificBuilder)
2929import Data.Text.Lazy.Builder
3030import Data.Text.Lazy.Builder.Int (decimal)
31−import Data.Text.Lazy.Builder.RealFloat (realFloat)
3231import Data.Text.Lazy.Encoding (encodeUtf8)
3332import Numeric (showHex)
3433import qualified Data.ByteString.Lazy as L
@@ -43,7 +42,7 @@ fromValue :: Value -> Builder
4342fromValue Null = {-# SCC "fromValue/Null" #-} "null"
4443fromValue (Bool b) = {-# SCC "fromValue/Bool" #-}
4544 if b then "true" else "false"
46−fromValue (Number n) = {-# SCC "fromValue/Number" #-} fromNumber n
45+fromValue (Number s) = {-# SCC "fromValue/Number" #-} fromScientific s
4746fromValue (String s) = {-# SCC "fromValue/String" #-} string s
4847fromValue (Array v)
4948 | V.null v = {-# SCC "fromValue/Array" #-} "[]"
@@ -87,11 +86,12 @@ string s = {-# SCC "string" #-} singleton '"' <> quote s <> singleton '"'
8786 | otherwise = singleton c
8887 where h = showHex (fromEnum c) ""
8988
90−fromNumber :: Number -> Builder
91−fromNumber (I i) = decimal i
92−fromNumber (D d)
93− | isNaN d || isInfinite d = "null"
94− | otherwise = realFloat d
89+fromScientific :: Scientific -> Builder
90+fromScientific s
91+ | e < 0 = scientificBuilder s
92+ | otherwise = decimal (coefficient s * 10 ^ e)
93+ where
94+ e = base10Exponent s
9595
9696-- | Efficiently serialize a JSON value as a lazy 'L.ByteString'.
9797encode :: ToJSON a => a -> L.ByteString
Data/Aeson/Parser/Internal.hs+2−2
@@ -136,7 +136,7 @@ arrayValues val = do
136136-- implementations in other languages conform to that same restriction
137137-- to preserve interoperability and security.
138138value :: Parser Value
139−value = most <|> (Number <$> number)
139+value = most <|> (Number <$> rational)
140140 where
141141 most = do
142142 c <- satisfy (`B8.elem` "{[\"ftn")
@@ -166,7 +166,7 @@ value' = most <|> num
166166 'n' -> string "ull" *> pure Null
167167 _ -> error "attoparsec panic! the impossible happened!"
168168 num = do
169− !n <- number
169+ !n <- rational
170170 return (Number n)
171171
172172doubleQuote, backslash :: Word8
Data/Aeson/Types.hs+1
@@ -47,6 +47,7 @@ module Data.Aeson.Types
4747 , withText
4848 , withArray
4949 , withNumber
50+ , withScientific
5051 , withBool
5152
5253 -- * Constructors and accessors
Data/Aeson/Types/Instances.hs+48−29
@@ -1,5 +1,5 @@
11{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, FlexibleInstances,
2− IncoherentInstances, OverlappingInstances,
2+ GeneralizedNewtypeDeriving, IncoherentInstances, OverlappingInstances,
33 OverloadedStrings, UndecidableInstances, ViewPatterns #-}
44{-# OPTIONS_GHC -fno-warn-orphans #-}
55
@@ -39,6 +39,7 @@ module Data.Aeson.Types.Instances
3939 , withText
4040 , withArray
4141 , withNumber
42+ , withScientific
4243 , withBool
4344
4445 -- * Functions
@@ -54,13 +55,14 @@ import Control.Applicative ((<$>), (<*>), (<|>), pure, empty)
5455import Data.Aeson.Functions
5556import Data.Aeson.Types.Class
5657import Data.Aeson.Types.Internal
57−import Data.Attoparsec.Char8 (Number(..))
58+import Data.Scientific (Scientific, coefficient, base10Exponent)
59+import Data.Attoparsec.Number (Number(..))
5860import Data.Fixed
5961import Data.Hashable (Hashable(..))
6062import Data.Int (Int8, Int16, Int32, Int64)
6163import Data.Maybe (fromMaybe)
6264import Data.Monoid (Dual(..), First(..), Last(..), mappend)
63−import Data.Ratio (Ratio)
65+import Data.Ratio (Ratio, (%), numerator, denominator)
6466import Data.Text (Text, pack, unpack)
6567import Data.Time (UTCTime, ZonedTime(..), TimeZone(..))
6668import Data.Time.Format (formatTime, parseTime)
@@ -150,48 +152,52 @@ instance FromJSON Char where
150152 {-# INLINE parseJSON #-}
151153
152154instance ToJSON Double where
153− toJSON = Number . D
155+ toJSON = realFloatToJSON
154156 {-# INLINE toJSON #-}
155157
158+realFloatToJSON :: RealFloat a => a -> Value
159+realFloatToJSON d
160+ | isNaN d || isInfinite d = Null
161+ | otherwise = Number $ realToFrac d
162+{-# INLINE realFloatToJSON #-}
163+
156164instance FromJSON Double where
157− parseJSON (Number n) = case n of
158− D d -> pure d
159− I i -> pure (fromIntegral i)
165+ parseJSON (Number s) = pure $ realToFrac s
160166 parseJSON Null = pure (0/0)
161167 parseJSON v = typeMismatch "Double" v
162168 {-# INLINE parseJSON #-}
163169
164170instance ToJSON Number where
165− toJSON = Number
171+ toJSON (D d) = toJSON d
172+ toJSON (I i) = Number $ fromInteger i
166173 {-# INLINE toJSON #-}
167174
168175instance FromJSON Number where
169− parseJSON (Number n) = pure n
176+ parseJSON (Number s) = pure $ scientificToNumber s
170177 parseJSON Null = pure (D (0/0))
171178 parseJSON v = typeMismatch "Number" v
172179 {-# INLINE parseJSON #-}
173180
174181instance ToJSON Float where
175− toJSON = Number . realToFrac
182+ toJSON = realFloatToJSON
176183 {-# INLINE toJSON #-}
177184
178185instance FromJSON Float where
179− parseJSON (Number n) = pure $ case n of
180− D d -> realToFrac d
181− I i -> fromIntegral i
186+ parseJSON (Number s) = pure $ realToFrac s
182187 parseJSON Null = pure (0/0)
183188 parseJSON v = typeMismatch "Float" v
184189 {-# INLINE parseJSON #-}
185190
186191instance ToJSON (Ratio Integer) where
187− toJSON = Number . fromRational
192+ toJSON r = object [ "numerator" .= numerator r
193+ , "denominator" .= denominator r
194+ ]
188195 {-# INLINE toJSON #-}
189196
190197instance FromJSON (Ratio Integer) where
191− parseJSON = withNumber "Ration Integer" $ \n ->
192− pure $ case n of
193− D d -> toRational d
194− I i -> fromIntegral i
198+ parseJSON = withObject "Rational" $ \obj ->
199+ (%) <$> obj .: "numerator"
200+ <*> obj .: "denominator"
195201 {-# INLINE parseJSON #-}
196202
197203instance HasResolution a => ToJSON (Fixed a) where
@@ -199,10 +205,7 @@ instance HasResolution a => ToJSON (Fixed a) where
199205 {-# INLINE toJSON #-}
200206
201207instance HasResolution a => FromJSON (Fixed a) where
202− parseJSON (Number n) = pure $ case n of
203− D d -> realToFrac d
204− I i -> fromIntegral i
205− parseJSON v = typeMismatch "Fixed" v
208+ parseJSON = withScientific "Fixed" $ pure . realToFrac
206209 {-# INLINE parseJSON #-}
207210
208211instance ToJSON Int where
@@ -214,7 +217,7 @@ instance FromJSON Int where
214217 {-# INLINE parseJSON #-}
215218
216219parseIntegral :: Integral a => Value -> Parser a
217−parseIntegral = withNumber "Integral" $ pure . floor
220+parseIntegral = withScientific "Integral" $ pure . floor
218221{-# INLINE parseIntegral #-}
219222
220223instance ToJSON Integer where
@@ -667,28 +670,35 @@ withObject _ f (Object obj) = f obj
667670withObject expected _ v = typeMismatch expected v
668671{-# INLINE withObject #-}
669672
670−-- | @withObject expected f value@ applies @f@ to the 'Text' when @value@ is a @String@
673+-- | @withText expected f value@ applies @f@ to the 'Text' when @value@ is a @String@
671674-- and fails using @'typeMismatch' expected@ otherwise.
672675withText :: String -> (Text -> Parser a) -> Value -> Parser a
673676withText _ f (String txt) = f txt
674677withText expected _ v = typeMismatch expected v
675678{-# INLINE withText #-}
676679
677−-- | @withObject expected f value@ applies @f@ to the 'Array' when @value@ is an @Array@
680+-- | @withArray expected f value@ applies @f@ to the 'Array' when @value@ is an @Array@
678681-- and fails using @'typeMismatch' expected@ otherwise.
679682withArray :: String -> (Array -> Parser a) -> Value -> Parser a
680683withArray _ f (Array arr) = f arr
681684withArray expected _ v = typeMismatch expected v
682685{-# INLINE withArray #-}
683686
684−-- | @withObject expected f value@ applies @f@ to the 'Number' when @value@ is a @Number@
687+-- | @withNumber expected f value@ applies @f@ to the 'Number' when @value@ is a 'Number'.
685688-- and fails using @'typeMismatch' expected@ otherwise.
686689withNumber :: String -> (Number -> Parser a) -> Value -> Parser a
687−withNumber _ f (Number num) = f num
688−withNumber expected _ v = typeMismatch expected v
690+withNumber expected f = withScientific expected (f . scientificToNumber)
689691{-# INLINE withNumber #-}
692+{-# DEPRECATED withNumber "Use withScientific instead" #-}
693+
694+-- | @withScientific expected f value@ applies @f@ to the 'Scientific' number when @value@ is a 'Number'.
695+-- and fails using @'typeMismatch' expected@ otherwise.
696+withScientific :: String -> (Scientific -> Parser a) -> Value -> Parser a
697+withScientific _ f (Number scientific) = f scientific
698+withScientific expected _ v = typeMismatch expected v
699+{-# INLINE withScientific #-}
690700
691−-- | @withObject expected f value@ applies @f@ to the 'Bool' when @value@ is a @Bool@
701+-- | @withBool expected f value@ applies @f@ to the 'Bool' when @value@ is a @Bool@
692702-- and fails using @'typeMismatch' expected@ otherwise.
693703withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
694704withBool _ f (Bool arr) = f arr
@@ -764,3 +774,12 @@ typeMismatch expected actual =
764774 Number _ -> "Number"
765775 Bool _ -> "Boolean"
766776 Null -> "Null"
777+
778+scientificToNumber :: Scientific -> Number
779+scientificToNumber s
780+ | e < 0 = D $ fromInteger c / 10 ^ negate e
781+ | otherwise = I $ c * 10 ^ e
782+ where
783+ e = base10Exponent s
784+ c = coefficient s
785+{-# INLINE scientificToNumber #-}
Data/Aeson/Types/Internal.hs+6−8
@@ -43,7 +43,7 @@ module Data.Aeson.Types.Internal
4343import Control.Applicative
4444import Control.Monad
4545import Control.DeepSeq (NFData(..))
46−import Data.Attoparsec.Char8 (Number(..))
46+import Data.Scientific (Scientific)
4747import Data.Hashable (Hashable(..))
4848import Data.HashMap.Strict (HashMap)
4949import Data.Monoid (Monoid(..))
@@ -171,7 +171,7 @@ type Array = Vector Value
171171data Value = Object !Object
172172 | Array !Array
173173 | String !Text
174− | Number !Number
174+ | Number !Scientific
175175 | Bool !Bool
176176 | Null
177177 deriving (Eq, Show, Typeable)
@@ -191,7 +191,7 @@ instance NFData Value where
191191 rnf (Object o) = rnf o
192192 rnf (Array a) = V.foldl' (\x y -> rnf y `seq` x) () a
193193 rnf (String s) = rnf s
194− rnf (Number n) = case n of I i -> rnf i; D d -> rnf d
194+ rnf (Number n) = rnf n
195195 rnf (Bool b) = rnf b
196196 rnf Null = ()
197197
@@ -205,11 +205,9 @@ instance Hashable Value where
205205 hashWithSalt s (Array a) = V.foldl' hashWithSalt
206206 (s `hashWithSalt` (1::Int)) a
207207 hashWithSalt s (String str) = s `hashWithSalt` (2::Int) `hashWithSalt` str
208− hashWithSalt s (Number n) = 3 `hashWithSalt`
209− case n of I i -> hashWithSalt s i
210− D d -> hashWithSalt s d
211− hashWithSalt s (Bool b) = s `hashWithSalt` (4::Int) `hashWithSalt` b
212− hashWithSalt s Null = s `hashWithSalt` (5::Int)
208+ hashWithSalt s (Number n) = s `hashWithSalt` (3::Int) `hashWithSalt` n
209+ hashWithSalt s (Bool b) = s `hashWithSalt` (4::Int) `hashWithSalt` b
210+ hashWithSalt s Null = s `hashWithSalt` (5::Int)
213211
214212-- | The empty array.
215213emptyArray :: Value
aeson.cabal+2−1
@@ -131,7 +131,8 @@ library
131131 text >= 0.11.1.0,
132132 time,
133133 unordered-containers >= 0.1.3.0,
134− vector >= 0.7.1
134+ vector >= 0.7.1,
135+ scientific >= 0.0 && < 0.1
135136
136137 if flag(blaze-builder)
137138 build-depends: blaze-builder >= 0.2.1.4
tests/Properties.hs+3−4
@@ -3,7 +3,6 @@
33import Data.Aeson.Encode
44import Data.Aeson.Parser (value)
55import Data.Aeson.Types
6−import Data.Attoparsec.Number
76import Test.Framework (Test, defaultMain, testGroup)
87import Test.Framework.Providers.QuickCheck2 (testProperty)
98import Test.QuickCheck (Arbitrary(..))
@@ -26,12 +25,12 @@ import qualified Data.Map as Map
2625
2726encodeDouble :: Double -> Double -> Bool
2827encodeDouble num denom
29− | isInfinite d || isNaN d = encode (Number (D d)) == "null"
30− | otherwise = (read . L.unpack . encode . Number . D) d == d
28+ | isInfinite d || isNaN d = encode d == "null"
29+ | otherwise = (read . L.unpack . encode) d == d
3130 where d = num / denom
3231
3332encodeInteger :: Integer -> Bool
34−encodeInteger i = encode (Number (I i)) == L.pack (show i)
33+encodeInteger i = encode i == L.pack (show i)
3534
3635toParseJSON :: (Arbitrary a, Eq a) => (Value -> Parser a) -> (a -> Value) -> a -> Bool
3736toParseJSON parsejson tojson x =