Skip to content

Commit 0d88558

Browse files
authored
Merge pull request #873 from haskell/double-serialize
Change +inf/-inf :: Double/Float serialization
2 parents 0514031 + 615054e commit 0d88558

File tree

5 files changed

+61
-22
lines changed

5 files changed

+61
-22
lines changed

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ For the latest version of this document, please see [https://github.com/haskell/
1515

1616
* Remove Data.Aeson.Encode module
1717

18+
* `Double` and `Float` infinities are encoded as `"+inf"` and `"-inf"`.
19+
Change `To/FromJSONKey` instances to use `"+inf"` and `"-inf"` too.
20+
1821
### 1.5.6.0
1922
* Make `Show Value` instance print object keys in lexicographic order.
2023

src/Data/Aeson/Encoding/Internal.hs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,20 @@ integer = Encoding . B.integerDec
294294
float :: Float -> Encoding
295295
float = realFloatToEncoding $ Encoding . B.floatDec
296296

297+
-- |
298+
--
299+
-- >>> double 42
300+
-- "42.0"
301+
--
302+
-- >>> double (0/0)
303+
-- "null"
304+
--
305+
-- >>> double (1/0)
306+
-- "\"+inf\""
307+
--
308+
-- >>> double (-23/0)
309+
-- "\"-inf\""
310+
--
297311
double :: Double -> Encoding
298312
double = realFloatToEncoding $ Encoding . B.doubleDec
299313

@@ -302,8 +316,9 @@ scientific = Encoding . EB.scientific
302316

303317
realFloatToEncoding :: RealFloat a => (a -> Encoding) -> a -> Encoding
304318
realFloatToEncoding e d
305-
| isNaN d || isInfinite d = null_
306-
| otherwise = e d
319+
| isNaN d = null_
320+
| isInfinite d = if d > 0 then Encoding "\"+inf\"" else Encoding "\"-inf\""
321+
| otherwise = e d
307322
{-# INLINE realFloatToEncoding #-}
308323

309324
-------------------------------------------------------------------------------
@@ -344,10 +359,28 @@ integerText :: Integer -> Encoding' a
344359
integerText = Encoding . EB.quote . B.integerDec
345360

346361
floatText :: Float -> Encoding' a
347-
floatText = Encoding . EB.quote . B.floatDec
362+
floatText d
363+
| isInfinite d = if d > 0 then Encoding "\"+inf\"" else Encoding "\"-inf\""
364+
| otherwise = Encoding . EB.quote . B.floatDec $ d
348365

366+
-- |
367+
--
368+
-- >>> doubleText 42
369+
-- "\"42.0\""
370+
--
371+
-- >>> doubleText (0/0)
372+
-- "\"NaN\""
373+
--
374+
-- >>> doubleText (1/0)
375+
-- "\"+inf\""
376+
--
377+
-- >>> doubleText (-23/0)
378+
-- "\"-inf\""
379+
--
349380
doubleText :: Double -> Encoding' a
350-
doubleText = Encoding . EB.quote . B.doubleDec
381+
doubleText d
382+
| isInfinite d = if d > 0 then Encoding "\"+inf\"" else Encoding "\"-inf\""
383+
| otherwise = Encoding . EB.quote . B.doubleDec $ d
351384

352385
scientificText :: Scientific -> Encoding' a
353386
scientificText = Encoding . EB.quote . EB.scientific

src/Data/Aeson/Types/FromJSON.hs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,11 @@ parseJSONElemAtIndex :: (Value -> Parser a) -> Int -> V.Vector Value -> Parser a
178178
parseJSONElemAtIndex p idx ary = p (V.unsafeIndex ary idx) <?> Index idx
179179

180180
parseRealFloat :: RealFloat a => String -> Value -> Parser a
181-
parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
182-
parseRealFloat _ Null = pure (0/0)
183-
parseRealFloat name v = prependContext name (unexpected v)
181+
parseRealFloat _ (Number s) = pure $ Scientific.toRealFloat s
182+
parseRealFloat _ Null = pure (0/0)
183+
parseRealFloat _ (String "-inf") = pure (negate 1/0)
184+
parseRealFloat _ (String "+inf") = pure (1/0)
185+
parseRealFloat name v = prependContext name (unexpected v)
184186

185187
parseIntegralFromScientific :: forall a. Integral a => Scientific -> Parser a
186188
parseIntegralFromScientific s =
@@ -1547,20 +1549,20 @@ instance FromJSON Double where
15471549

15481550
instance FromJSONKey Double where
15491551
fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1550-
"NaN" -> pure (0/0)
1551-
"Infinity" -> pure (1/0)
1552-
"-Infinity" -> pure (negate 1/0)
1553-
_ -> Scientific.toRealFloat <$> parseScientificText t
1552+
"NaN" -> pure (0/0)
1553+
"-inf" -> pure (1/0)
1554+
"+inf" -> pure (negate 1/0)
1555+
_ -> Scientific.toRealFloat <$> parseScientificText t
15541556

15551557
instance FromJSON Float where
15561558
parseJSON = parseRealFloat "Float"
15571559

15581560
instance FromJSONKey Float where
15591561
fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1560-
"NaN" -> pure (0/0)
1561-
"Infinity" -> pure (1/0)
1562-
"-Infinity" -> pure (negate 1/0)
1563-
_ -> Scientific.toRealFloat <$> parseScientificText t
1562+
"NaN" -> pure (0/0)
1563+
"+inf" -> pure (1/0)
1564+
"-inf" -> pure (negate 1/0)
1565+
_ -> Scientific.toRealFloat <$> parseScientificText t
15641566

15651567
instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
15661568
parseJSON (Number x)

src/Data/Aeson/Types/ToJSON.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
146146

147147
realFloatToJSON :: RealFloat a => a -> Value
148148
realFloatToJSON d
149-
| isNaN d || isInfinite d = Null
150-
| otherwise = Number $ Scientific.fromFloatDigits d
149+
| isNaN d = Null
150+
| isInfinite d = if d > 0 then "+inf" else "-inf"
151+
| otherwise = Number $ Scientific.fromFloatDigits d
151152

152153
-------------------------------------------------------------------------------
153154
-- Generics

tests/SerializationFormatSpec.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ jsonExamples =
109109
(M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)
110110

111111
, example "nan :: Double" "null" (Approx $ 0/0 :: Approx Double)
112+
, example "+inf :: Double" "\"+inf\"" (Approx $ 1/0 :: Approx Double)
113+
, example "-inf :: Double" "\"-inf\"" (Approx $ -1/0 :: Approx Double)
112114

113115
, example "Ordering LT" "\"LT\"" LT
114116
, example "Ordering EQ" "\"EQ\"" EQ
@@ -130,8 +132,8 @@ jsonExamples =
130132

131133
-- Three separate cases, as ordering in HashMap is not defined
132134
, example "HashMap Float Int, NaN" "{\"NaN\":1}" (Approx $ HM.singleton (0/0) 1 :: Approx (HM.HashMap Float Int))
133-
, example "HashMap Float Int, Infinity" "{\"Infinity\":1}" (HM.singleton (1/0) 1 :: HM.HashMap Float Int)
134-
, example "HashMap Float Int, +Infinity" "{\"-Infinity\":1}" (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)
135+
, example "HashMap Float Int, Infinity" "{\"+inf\":1}" (HM.singleton (1/0) 1 :: HM.HashMap Float Int)
136+
, example "HashMap Float Int, +Infinity" "{\"-inf\":1}" (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)
135137

136138
-- Functors
137139
, example "Identity Int" "1" (pure 1 :: Identity Int)
@@ -258,12 +260,10 @@ jsonExamples =
258260
$ F.unfoldNu F.unFix $ F.Fix (S.These True (F.Fix (S.That (F.Fix (S.This False)))))
259261
]
260262

263+
-- encodings which clash (like infinities prior aeson-2.0)
261264
jsonEncodingExamples :: [Example]
262265
jsonEncodingExamples =
263266
[
264-
-- infinities cannot be recovered, null is decoded as NaN
265-
example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)
266-
, example "-inf :: Double" "null" (Approx $ -1/0 :: Approx Double)
267267
]
268268

269269
jsonDecodingExamples :: [Example]

0 commit comments

Comments
 (0)