on master help sign in
Allow FromJSON instance for Ratio a parse numbers (#745)
f2306d8Aleksey Khudyakov
Data/Aeson/Types/FromJSON.hs+16−6
@@ -1541,12 +1541,22 @@ instance FromJSONKey Float where
1541 _ -> Scientific.toRealFloat <$> parseScientificText t
1541 _ -> Scientific.toRealFloat <$> parseScientificText t
1542
1542
1543instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
1543instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
1544+ parseJSON (Number x)
1545+ | exp10 <= 1024
1546+ , exp10 >= -1024 = return $! realToFrac x
1547+ | otherwise = prependContext "Ratio" $ fail msg
1548+ where
1549+ exp10 = base10Exponent x
1550+ msg = "found a number with exponent " ++ show exp10
1551+ ++ ", but it must not be greater than 1024 or less than -1024"
1552+ parseJSON o = objParser o
1553+ where
1544− parseJSON = withObject "Rational" $ \obj -> do
1554+ objParser = withObject "Rational" $ \obj -> do
1545− numerator <- obj .: "numerator"
1555+ numerator <- obj .: "numerator"
1546− denominator <- obj .: "denominator"
1556+ denominator <- obj .: "denominator"
1547− if denominator == 0
1557+ if denominator == 0
1548− then fail "Ratio denominator was 0"
1558+ then fail "Ratio denominator was 0"
1549− else pure $ numerator % denominator
1559+ else pure $ numerator % denominator
1550 {-# INLINE parseJSON #-}
1560 {-# INLINE parseJSON #-}
1551
1561
1552-- | This instance includes a bounds check to prevent maliciously
1562-- | This instance includes a bounds check to prevent maliciously
Data/Aeson/Types/ToJSON.hs−1
@@ -100,7 +100,6 @@ import GHC.Generics
100import Numeric.Natural (Natural)
100import Numeric.Natural (Natural)
101import qualified Data.Aeson.Encoding as E
101import qualified Data.Aeson.Encoding as E
102import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
102import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding)
103−import qualified Data.ByteString as S
104import qualified Data.ByteString.Lazy as L
103import qualified Data.ByteString.Lazy as L
105import qualified Data.DList as DList
104import qualified Data.DList as DList
106import qualified Data.HashMap.Strict as H
105import qualified Data.HashMap.Strict as H
changelog.md+5
@@ -1,5 +1,10 @@
1For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/changelog.md](https://github.com/bos/aeson/blob/master/changelog.md).
1For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/changelog.md](https://github.com/bos/aeson/blob/master/changelog.md).
2
2
3+### upcoming
4+
5+* `FromJSON` instance of `Ratio a` now parses numbers in addtion to
6+ standard `{numerator=..., denumerator=...}` encoding.
7+
3### 1.4.6.0
8### 1.4.6.0
4
9
5* Provide a clearer error message when a required tagKey for a constructor is missing, thanks to Guru Devanla.
10* Provide a clearer error message when a required tagKey for a constructor is missing, thanks to Guru Devanla.
tests/UnitTests.hs+22
@@ -114,6 +114,9 @@ tests = testGroup "unit" [
114 , testCase "SingleFieldCon" singleFieldCon
114 , testCase "SingleFieldCon" singleFieldCon
115 , testGroup "Ordering of object keys" keyOrdering
115 , testGroup "Ordering of object keys" keyOrdering
116 , testCase "Ratio with denominator 0" ratioDenominator0
116 , testCase "Ratio with denominator 0" ratioDenominator0
117+ , testCase "Rational parses number" rationalNumber
118+ , testCase "Big rational" bigRationalDecoding
119+ , testCase "Small rational" smallRationalDecoding
117 , testCase "Big scientific exponent" bigScientificExponent
120 , testCase "Big scientific exponent" bigScientificExponent
118 , testCase "Big integer decoding" bigIntegerDecoding
121 , testCase "Big integer decoding" bigIntegerDecoding
119 , testCase "Big natural decading" bigNaturalDecoding
122 , testCase "Big natural decading" bigNaturalDecoding
@@ -620,6 +623,25 @@ ratioDenominator0 =
620 (Left "Error in $: Ratio denominator was 0")
623 (Left "Error in $: Ratio denominator was 0")
621 (eitherDecode "{ \"numerator\": 1, \"denominator\": 0 }" :: Either String Rational)
624 (eitherDecode "{ \"numerator\": 1, \"denominator\": 0 }" :: Either String Rational)
622
625
626+rationalNumber :: Assertion
627+rationalNumber =
628+ assertEqual "Ratio with denominator 0"
629+ (Right 1.37)
630+ (eitherDecode "1.37" :: Either String Rational)
631+
632+bigRationalDecoding :: Assertion
633+bigRationalDecoding =
634+ assertEqual "Decoding an Integer with a large exponent should fail"
635+ (Left "Error in $: parsing Ratio failed, found a number with exponent 2000, but it must not be greater than 1024 or less than -1024")
636+ ((eitherDecode :: L.ByteString -> Either String Rational) "1e2000")
637+
638+smallRationalDecoding :: Assertion
639+smallRationalDecoding =
640+ assertEqual "Decoding an Integer with a large exponent should fail"
641+ (Left "Error in $: parsing Ratio failed, found a number with exponent -2000, but it must not be greater than 1024 or less than -1024")
642+ ((eitherDecode :: L.ByteString -> Either String Rational) "1e-2000")
643+
644+
623bigScientificExponent :: Assertion
645bigScientificExponent :: Assertion
624bigScientificExponent =
646bigScientificExponent =
625 assertEqual "Encoding an integral scientific with a large exponent should normalize it"
647 assertEqual "Encoding an integral scientific with a large exponent should normalize it"