on master help sign in
Numeric types

- FromJSONKey / ToJSONKey for numeric types - Generalise Ratio Integer -> Ratio a instances - numeric Encodings

commit8faba18bdc75b447da195d42a5fb12982ff88925 authorOleg Grenrus <oleg.grenrus@iki.fi> dateWed 8 Jun 2016, 10:03 (10 years ago) parentf934e7a
7 files changed+349−91
Data/Aeson/Encode/Builder.hs+4−4 Data/Aeson/Encoding.hs+11 Data/Aeson/Encoding/Internal.hs+136−16 Data/Aeson/Types/FromJSON.hs+76−21 Data/Aeson/Types/ToJSON.hs+106−46 tests/Properties.hs+8−4 tests/UnitTests.hs+8
Data/Aeson/Encode/Builder.hs+4−4
@@ -24,7 +24,7 @@ module Data.Aeson.Encode.Builder
2424 , string
2525 , unquoted
2626 , quote
27− , number
27+ , scientific
2828 , day
2929 , localTime
3030 , utcTime
@@ -59,7 +59,7 @@ import qualified Data.Vector as V
5959encodeToBuilder :: Value -> Builder
6060encodeToBuilder Null = null_
6161encodeToBuilder (Bool b) = bool b
62−encodeToBuilder (Number n) = number n
62+encodeToBuilder (Number n) = scientific n
6363encodeToBuilder (String s) = text s
6464encodeToBuilder (Array v) = array v
6565encodeToBuilder (Object m) = object m
@@ -128,8 +128,8 @@ c2w :: Char -> Word8
128128c2w c = fromIntegral (ord c)
129129
130130-- | Encode a JSON number.
131−number :: Scientific -> Builder
132−number s
131+scientific :: Scientific -> Builder
132+scientific s
133133 | e < 0 = scientificBuilder s
134134 | otherwise = B.integerDec (coefficient s * 10 ^ e)
135135 where
Data/Aeson/Encoding.hs+11
@@ -14,6 +14,17 @@ module Data.Aeson.Encoding
1414 , text
1515 , list
1616 , dict
17+ , null_
18+ , bool
19+ -- ** Decimal numbers
20+ , int8, int16, int32, int64, int
21+ , word8, word16, word32, word64, word
22+ , integer, float, double, scientific
23+
24+ -- ** Decimal numbers as Text
25+ , int8Text, int16Text, int32Text, int64Text, intText
26+ , word8Text, word16Text, word32Text, word64Text, wordText
27+ , integerText, floatText, doubleText, scientificText
1728 ) where
1829
1930import Data.Aeson.Encoding.Internal
Data/Aeson/Encoding/Internal.hs+136−16
@@ -1,9 +1,11 @@
1−{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, DeriveDataTypeable, RankNTypes #-}
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE RankNTypes #-}
4+{-# LANGUAGE OverloadedStrings #-}
25module Data.Aeson.Encoding.Internal
36 (
47 -- * Encoding
58 Encoding (..) -- TODO: export fromEncoding for now
6− , fromEncoding
79 , unsafeToEncoding
810 , Series (..) -- TODO: don't export constructor
911 , pairs
@@ -14,27 +16,40 @@ module Data.Aeson.Encoding.Internal
1416 , emptyObject_
1517 , wrapArray
1618 , wrapObject
19+ , null_
20+ , bool
1721 , text
1822 , list
1923 , dict
2024 , tuple
2125 , (>*<)
22− -- ** chars
26+ -- ** Decimal numbers
27+ , int8, int16, int32, int64, int
28+ , word8, word16, word32, word64, word
29+ , integer, float, double, scientific
30+ -- ** Decimal numbers as Text
31+ , int8Text, int16Text, int32Text, int64Text, intText
32+ , word8Text, word16Text, word32Text, word64Text, wordText
33+ , integerText, floatText, doubleText, scientificText
34+ -- ** JSON tokens
2335 , comma, colon, openBracket, closeBracket, openCurly, closeCurly
2436 ) where
2537
26−#if !MIN_VERSION_base(4,8,0)
27−import Data.Monoid (Monoid(..))
28−#endif
38+import Prelude ()
39+import Prelude.Compat
2940
30−import Data.Text (Text)
31−import Data.ByteString.Builder (Builder, char7, toLazyByteString)
32−import Data.Semigroup (Semigroup((<>)))
41+import Data.ByteString.Builder (Builder, char7, toLazyByteString)
3342import Data.ByteString.Builder.Prim (primBounded)
34−import Data.Typeable (Typeable)
43+import Data.Int
44+import Data.Scientific (Scientific)
45+import Data.Semigroup (Semigroup ((<>)))
46+import Data.Text (Text)
47+import Data.Typeable (Typeable)
48+import Data.Word
3549
36−import qualified Data.ByteString.Lazy as BSL
37−import qualified Data.Aeson.Encode.Builder as B
50+import qualified Data.Aeson.Encode.Builder as EB
51+import qualified Data.ByteString.Builder as B
52+import qualified Data.ByteString.Lazy as BSL
3853
3954-- | An encoding of a JSON value.
4055newtype Encoding = Encoding {
@@ -90,10 +105,10 @@ nullEncoding :: Encoding -> Bool
90105nullEncoding = BSL.null . toLazyByteString . fromEncoding
91106
92107emptyArray_ :: Encoding
93−emptyArray_ = Encoding B.emptyArray_
108+emptyArray_ = Encoding EB.emptyArray_
94109
95110emptyObject_ :: Encoding
96−emptyObject_ = Encoding B.emptyObject_
111+emptyObject_ = Encoding EB.emptyObject_
97112
98113wrapArray :: Encoding -> Encoding
99114wrapArray e = openBracket <> e <> closeBracket
@@ -101,6 +116,13 @@ wrapArray e = openBracket <> e <> closeBracket
101116wrapObject :: Encoding -> Encoding
102117wrapObject e = openCurly <> e <> closeCurly
103118
119+null_ :: Encoding
120+null_ = Encoding EB.null_
121+
122+bool :: Bool -> Encoding
123+bool True = Encoding "true"
124+bool False = Encoding "false"
125+
104126-- | Encode a series of key/value pairs, separated by commas.
105127pairs :: Series -> Encoding
106128pairs = brackets '{' '}'
@@ -109,7 +131,7 @@ pairs = brackets '{' '}'
109131brackets :: Char -> Char -> Series -> Encoding
110132brackets begin end (Value v) = Encoding $
111133 char7 begin <> fromEncoding v <> char7 end
112−brackets begin end Empty = Encoding (primBounded (B.ascii2 (begin,end)) ())
134+brackets begin end Empty = Encoding (primBounded (EB.ascii2 (begin,end)) ())
113135
114136list :: (a -> Encoding) -> [a] -> Encoding
115137list _ [] = emptyArray_
@@ -150,7 +172,7 @@ tuple b = Encoding (char7 '[' <> fromEncoding b <> char7 ']')
150172{-# INLINE tuple #-}
151173
152174text :: Text -> Encoding
153−text = Encoding . B.text
175+text = Encoding . EB.text
154176
155177-------------------------------------------------------------------------------
156178-- chars
@@ -164,3 +186,101 @@ closeBracket = Encoding $ char7 ']'
164186openCurly = Encoding $ char7 '{'
165187closeCurly = Encoding $ char7 '}'
166188
189+-------------------------------------------------------------------------------
190+-- Decimal numbers
191+-------------------------------------------------------------------------------
192+
193+int8 :: Int8 -> Encoding
194+int8 = Encoding . B.int8Dec
195+
196+int16 :: Int16 -> Encoding
197+int16 = Encoding . B.int16Dec
198+
199+int32 :: Int32 -> Encoding
200+int32 = Encoding . B.int32Dec
201+
202+int64 :: Int64 -> Encoding
203+int64 = Encoding . B.int64Dec
204+
205+int :: Int -> Encoding
206+int = Encoding . B.intDec
207+
208+word8 :: Word8 -> Encoding
209+word8 = Encoding . B.word8Dec
210+
211+word16 :: Word16 -> Encoding
212+word16 = Encoding . B.word16Dec
213+
214+word32 :: Word32 -> Encoding
215+word32 = Encoding . B.word32Dec
216+
217+word64 :: Word64 -> Encoding
218+word64 = Encoding . B.word64Dec
219+
220+word :: Word -> Encoding
221+word = Encoding . B.wordDec
222+
223+integer :: Integer -> Encoding
224+integer = Encoding . B.integerDec
225+
226+float :: Float -> Encoding
227+float = realFloatToEncoding $ Encoding . B.floatDec
228+
229+double :: Double -> Encoding
230+double = realFloatToEncoding $ Encoding . B.doubleDec
231+
232+scientific :: Scientific -> Encoding
233+scientific = Encoding . EB.scientific
234+
235+realFloatToEncoding :: RealFloat a => (a -> Encoding) -> a -> Encoding
236+realFloatToEncoding e d
237+ | isNaN d || isInfinite d = null_
238+ | otherwise = e d
239+{-# INLINE realFloatToEncoding #-}
240+
241+-------------------------------------------------------------------------------
242+-- Decimal numbers as Text
243+-------------------------------------------------------------------------------
244+
245+int8Text :: Int8 -> Encoding
246+int8Text = Encoding . EB.quote . B.int8Dec
247+
248+int16Text :: Int16 -> Encoding
249+int16Text = Encoding . EB.quote . B.int16Dec
250+
251+int32Text :: Int32 -> Encoding
252+int32Text = Encoding . EB.quote . B.int32Dec
253+
254+int64Text :: Int64 -> Encoding
255+int64Text = Encoding . EB.quote . B.int64Dec
256+
257+intText :: Int -> Encoding
258+intText = Encoding . EB.quote . B.intDec
259+
260+word8Text :: Word8 -> Encoding
261+word8Text = Encoding . EB.quote . B.word8Dec
262+
263+word16Text :: Word16 -> Encoding
264+word16Text = Encoding . EB.quote . B.word16Dec
265+
266+word32Text :: Word32 -> Encoding
267+word32Text = Encoding . EB.quote . B.word32Dec
268+
269+word64Text :: Word64 -> Encoding
270+word64Text = Encoding . EB.quote . B.word64Dec
271+
272+wordText :: Word -> Encoding
273+wordText = Encoding . EB.quote . B.wordDec
274+
275+integerText :: Integer -> Encoding
276+integerText = Encoding . EB.quote . B.integerDec
277+
278+-- | TODO: check infinite and nan?
279+floatText :: Float -> Encoding
280+floatText = Encoding . EB.quote . B.floatDec
281+
282+doubleText :: Double -> Encoding
283+doubleText = Encoding . EB.quote . B.doubleDec
284+
285+scientificText :: Scientific -> Encoding
286+scientificText = Encoding . EB.quote . EB.scientific
Data/Aeson/Types/FromJSON.hs+76−21
@@ -73,7 +73,9 @@ import Data.Aeson.Functions (mapKey)
7373import Data.Aeson.Types.Generic
7474import Data.Aeson.Types.Internal
7575
76−import qualified Data.Aeson.Parser.Time as Time
76+import qualified Data.Aeson.Parser.Time as Time
77+import qualified Data.Attoparsec.ByteString.Char8 as A (endOfInput, parseOnly,
78+ scientific)
7779
7880import Control.Applicative (Const (..), (<|>))
7981import Control.Monad ((<=<))
@@ -114,8 +116,8 @@ import qualified Data.Scientific as Scientific
114116import qualified Data.Sequence as Seq
115117import qualified Data.Set as Set
116118import qualified Data.Text as T
119+import qualified Data.Text.Encoding as T
117120import qualified Data.Text.Lazy as LT
118−import qualified Data.Text.Read as TR
119121import qualified Data.Tree as Tree
120122import qualified Data.Vector as V
121123import qualified Data.Vector.Generic as VG
@@ -177,6 +179,15 @@ parseIntegral :: Integral a => String -> Value -> Parser a
177179parseIntegral expected = withScientific expected $ pure . truncate
178180{-# INLINE parseIntegral #-}
179181
182+parseScientificText :: Text -> Parser Scientific
183+parseScientificText
184+ = either fail pure
185+ . A.parseOnly (A.scientific <* A.endOfInput)
186+ . T.encodeUtf8
187+
188+parseIntegralText :: Integral a => Text -> Parser a
189+parseIntegralText t = truncate <$> parseScientificText t
190+
180191parseOptionalFieldWith :: (Value -> Parser (Maybe a))
181192 -> Object -> Text -> Parser (Maybe a)
182193parseOptionalFieldWith pj obj key =
@@ -1000,6 +1011,12 @@ instance FromJSON Bool where
10001011 parseJSON = withBool "Bool" pure
10011012 {-# INLINE parseJSON #-}
10021013
1014+instance FromJSONKey Bool where
1015+ fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1016+ "true" -> pure True
1017+ "false" -> pure False
1018+ _ -> fail $ "Cannot parse key into Bool: " ++ T.unpack t
1019+
10031020instance FromJSON Ordering where
10041021 parseJSON = withText "Ordering" $ \s ->
10051022 case s of
@@ -1029,6 +1046,13 @@ instance FromJSON Double where
10291046 parseJSON = parseRealFloat "Double"
10301047 {-# INLINE parseJSON #-}
10311048
1049+instance FromJSONKey Double where
1050+ fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1051+ "NaN" -> pure (0/0)
1052+ "Infinity" -> pure (1/0)
1053+ "-Infinity" -> pure (negate 1/0)
1054+ _ -> Scientific.toRealFloat <$> parseScientificText t
1055+
10321056instance FromJSON Number where
10331057 parseJSON (Number s) = pure $ scientificToNumber s
10341058 parseJSON Null = pure (D (0/0))
@@ -1039,7 +1063,14 @@ instance FromJSON Float where
10391063 parseJSON = parseRealFloat "Float"
10401064 {-# INLINE parseJSON #-}
10411065
1042−instance FromJSON (Ratio Integer) where
1066+instance FromJSONKey Float where
1067+ fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1068+ "NaN" -> pure (0/0)
1069+ "Infinity" -> pure (1/0)
1070+ "-Infinity" -> pure (negate 1/0)
1071+ _ -> Scientific.toRealFloat <$> parseScientificText t
1072+
1073+instance (FromJSON a, Integral a) => FromJSON (Ratio a) where
10431074 parseJSON = withObject "Rational" $ \obj ->
10441075 (%) <$> obj .: "numerator"
10451076 <*> obj .: "denominator"
@@ -1057,6 +1088,9 @@ instance FromJSON Int where
10571088 parseJSON = parseIntegral "Int"
10581089 {-# INLINE parseJSON #-}
10591090
1091+instance FromJSONKey Int where
1092+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1093+
10601094-- | /WARNING:/ Only parse Integers from trusted input since an
10611095-- attacker could easily fill up the memory of the target system by
10621096-- specifying a scientific number with a big exponent like
@@ -1065,52 +1099,91 @@ instance FromJSON Integer where
10651099 parseJSON = withScientific "Integral" $ pure . truncate
10661100 {-# INLINE parseJSON #-}
10671101
1102+instance FromJSONKey Integer where
1103+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1104+
10681105instance FromJSON Natural where
10691106 parseJSON = withScientific "Natural" $ \s ->
10701107 if Scientific.coefficient s < 0
10711108 then fail $ "Expected a Natural number but got the negative number: " <> show s
10721109 else pure $ truncate s
10731110
1111+instance FromJSONKey Natural where
1112+ fromJSONKey = FromJSONKeyTextParser $ \t -> parseScientificText t >>= \s ->
1113+ if Scientific.coefficient s < 0
1114+ then fail $ "Expected a Natural number but got the negative number: " <> show s
1115+ else pure $ truncate s
1116+
10741117instance FromJSON Int8 where
10751118 parseJSON = parseIntegral "Int8"
10761119 {-# INLINE parseJSON #-}
10771120
1121+instance FromJSONKey Int8 where
1122+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1123+
10781124instance FromJSON Int16 where
10791125 parseJSON = parseIntegral "Int16"
10801126 {-# INLINE parseJSON #-}
10811127
1128+instance FromJSONKey Int16 where
1129+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1130+
10821131instance FromJSON Int32 where
10831132 parseJSON = parseIntegral "Int32"
10841133 {-# INLINE parseJSON #-}
10851134
1135+instance FromJSONKey Int32 where
1136+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1137+
10861138instance FromJSON Int64 where
10871139 parseJSON = parseIntegral "Int64"
10881140 {-# INLINE parseJSON #-}
10891141
1142+instance FromJSONKey Int64 where
1143+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1144+
10901145instance FromJSON Word where
10911146 parseJSON = parseIntegral "Word"
10921147 {-# INLINE parseJSON #-}
10931148
1149+instance FromJSONKey Word where
1150+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1151+
10941152instance FromJSON Word8 where
10951153 parseJSON = parseIntegral "Word8"
10961154 {-# INLINE parseJSON #-}
10971155
1156+instance FromJSONKey Word8 where
1157+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1158+
10981159instance FromJSON Word16 where
10991160 parseJSON = parseIntegral "Word16"
11001161 {-# INLINE parseJSON #-}
11011162
1163+instance FromJSONKey Word16 where
1164+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1165+
11021166instance FromJSON Word32 where
11031167 parseJSON = parseIntegral "Word32"
11041168 {-# INLINE parseJSON #-}
11051169
1170+instance FromJSONKey Word32 where
1171+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1172+
11061173instance FromJSON Word64 where
11071174 parseJSON = parseIntegral "Word64"
11081175 {-# INLINE parseJSON #-}
11091176
1177+instance FromJSONKey Word64 where
1178+ fromJSONKey = FromJSONKeyTextParser parseIntegralText
1179+
11101180instance FromJSON Text where
11111181 parseJSON = withText "Text" pure
11121182 {-# INLINE parseJSON #-}
11131183
1184+instance FromJSONKey Text where
1185+ fromJSONKey = fromJSONKeyCoerce
1186+
11141187instance FromJSON LT.Text where
11151188 parseJSON = withText "Lazy Text" $ pure . LT.fromStrict
11161189 {-# INLINE parseJSON #-}
@@ -1385,24 +1458,6 @@ instance FromJSONKey b => FromJSONKey (Tagged a b) where
13851458-- Instances for converting from map keys
13861459-------------------------------------------------------------------------------
13871460
1388−instance FromJSONKey Text where
1389− fromJSONKey = fromJSONKeyCoerce
1390−
1391−instance FromJSONKey Bool where
1392− fromJSONKey = FromJSONKeyTextParser $ \t -> case t of
1393− "true" -> pure True
1394− "false" -> pure False
1395− _ -> fail $ "Cannot parse key into Bool: " ++ T.unpack t
1396−
1397−instance FromJSONKey Int where
1398− -- not sure if there if there is already a helper in
1399− -- aeson for doing this.
1400− fromJSONKey = FromJSONKeyTextParser $ \t -> case TR.signed TR.decimal t of
1401− Left err -> fail err
1402− Right (v,t2) -> if T.null t2
1403− then return v
1404− else fail "Was not an integer, had extra stuff."
1405−
14061461instance (FromJSON a, FromJSON b) => FromJSONKey (a,b)
14071462instance (FromJSON a, FromJSON b, FromJSON c) => FromJSONKey (a,b,c)
14081463instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSONKey (a,b,c,d)
Data/Aeson/Types/ToJSON.hs+106−46
@@ -107,8 +107,6 @@ import qualified Data.Set as Set
107107import qualified Data.Text as T
108108import qualified Data.Text.Encoding as T
109109import qualified Data.Text.Lazy as LT
110−import qualified Data.Text.Lazy.Builder as LTB
111−import qualified Data.Text.Lazy.Builder.Int as LTBI
112110import qualified Data.Tree as Tree
113111import qualified Data.Vector as V
114112import qualified Data.Vector.Generic as VG
@@ -134,12 +132,6 @@ realFloatToJSON d
134132 | otherwise = Number $ Scientific.fromFloatDigits d
135133{-# INLINE realFloatToJSON #-}
136134
137−realFloatToEncoding :: RealFloat a => a -> Encoding
138−realFloatToEncoding d
139− | isNaN d || isInfinite d = Encoding EB.null_
140− | otherwise = toEncoding (Scientific.fromFloatDigits d)
141−{-# INLINE realFloatToEncoding #-}
142−
143135-------------------------------------------------------------------------------
144136-- Generics
145137-------------------------------------------------------------------------------
@@ -1139,9 +1131,15 @@ instance ToJSON Bool where
11391131 toJSON = Bool
11401132 {-# INLINE toJSON #-}
11411133
1142− toEncoding = Encoding . EB.bool
1134+ toEncoding = E.bool
11431135 {-# INLINE toEncoding #-}
11441136
1137+instance ToJSONKey Bool where
1138+ toJSONKey = ToJSONKeyText
1139+ ( (\x -> if x then "true" else "false")
1140+ , (\x -> toEncoding $ if x then ("true" :: Text) else "false")
1141+ )
1142+
11451143
11461144instance ToJSON Ordering where
11471145 toJSON = toJSON . orderingToText
@@ -1179,9 +1177,13 @@ instance ToJSON Double where
11791177 toJSON = realFloatToJSON
11801178 {-# INLINE toJSON #-}
11811179
1182− toEncoding = realFloatToEncoding
1180+ toEncoding = E.double
11831181 {-# INLINE toEncoding #-}
11841182
1183+instance ToJSONKey Double where
1184+ toJSONKey = toJSONKeyTextEnc E.doubleText
1185+ {-# INLINE toJSONKey #-}
1186+
11851187
11861188instance ToJSON Number where
11871189 toJSON (D d) = toJSON d
@@ -1197,20 +1199,23 @@ instance ToJSON Float where
11971199 toJSON = realFloatToJSON
11981200 {-# INLINE toJSON #-}
11991201
1200− toEncoding = realFloatToEncoding
1202+ toEncoding = E.float
12011203 {-# INLINE toEncoding #-}
12021204
1205+instance ToJSONKey Float where
1206+ toJSONKey = toJSONKeyTextEnc E.floatText
1207+ {-# INLINE toJSONKey #-}
1208+
12031209
1204−instance ToJSON (Ratio Integer) where
1210+instance (ToJSON a, Integral a) => ToJSON (Ratio a) where
12051211 toJSON r = object [ "numerator" .= numerator r
12061212 , "denominator" .= denominator r
12071213 ]
12081214 {-# INLINE toJSON #-}
12091215
1210− toEncoding r = Encoding $
1211− B.shortByteString "{\"numerator\":" <> builder (numerator r) <>
1212− B.shortByteString ",\"denominator\":" <> builder (denominator r) <>
1213− B.char7 '}'
1216+ toEncoding r = E.pairs $
1217+ "numerator" .= numerator r <>
1218+ "denominator" .= denominator r
12141219 {-# INLINE toEncoding #-}
12151220
12161221
@@ -1218,24 +1223,37 @@ instance HasResolution a => ToJSON (Fixed a) where
12181223 toJSON = Number . realToFrac
12191224 {-# INLINE toJSON #-}
12201225
1221− toEncoding = Encoding . EB.number . realToFrac
1226+ toEncoding = E.scientific . realToFrac
12221227 {-# INLINE toEncoding #-}
12231228
1229+instance HasResolution a => ToJSONKey (Fixed a) where
1230+ toJSONKey = toJSONKeyTextEnc (E.scientificText . realToFrac)
1231+ {-# INLINE toJSONKey #-}
1232+
12241233
12251234instance ToJSON Int where
12261235 toJSON = Number . fromIntegral
12271236 {-# INLINE toJSON #-}
12281237
1229− toEncoding = Encoding . B.intDec
1238+ toEncoding = E.int
12301239 {-# INLINE toEncoding #-}
12311240
1241+instance ToJSONKey Int where
1242+ toJSONKey = toJSONKeyTextEnc E.intText
1243+ {-# INLINE toJSONKey #-}
1244+
1245+
12321246instance ToJSON Integer where
12331247 toJSON = Number . fromInteger
12341248 {-# INLINE toJSON #-}
12351249
1236− toEncoding = Encoding . B.integerDec
1250+ toEncoding = E.integer
12371251 {-# INLINE toEncoding #-}
12381252
1253+instance ToJSONKey Integer where
1254+ toJSONKey = toJSONKeyTextEnc E.integerText
1255+ {-# INLINE toJSONKey #-}
1256+
12391257
12401258instance ToJSON Natural where
12411259 toJSON = toJSON . toInteger
@@ -1244,84 +1262,130 @@ instance ToJSON Natural where
12441262 toEncoding = toEncoding . toInteger
12451263 {-# INLINE toEncoding #-}
12461264
1265+instance ToJSONKey Natural where
1266+ toJSONKey = toJSONKeyTextEnc (E.integerText . toInteger)
1267+ {-# INLINE toJSONKey #-}
1268+
12471269
12481270instance ToJSON Int8 where
12491271 toJSON = Number . fromIntegral
12501272 {-# INLINE toJSON #-}
12511273
1252− toEncoding = Encoding . B.int8Dec
1274+ toEncoding = E.int8
12531275 {-# INLINE toEncoding #-}
12541276
1277+instance ToJSONKey Int8 where
1278+ toJSONKey = toJSONKeyTextEnc E.int8Text
1279+ {-# INLINE toJSONKey #-}
1280+
12551281
12561282instance ToJSON Int16 where
12571283 toJSON = Number . fromIntegral
12581284 {-# INLINE toJSON #-}
12591285
1260− toEncoding = Encoding . B.int16Dec
1286+ toEncoding = E.int16
12611287 {-# INLINE toEncoding #-}
12621288
1289+instance ToJSONKey Int16 where
1290+ toJSONKey = toJSONKeyTextEnc E.int16Text
1291+ {-# INLINE toJSONKey #-}
1292+
12631293
12641294instance ToJSON Int32 where
12651295 toJSON = Number . fromIntegral
12661296 {-# INLINE toJSON #-}
12671297
1268− toEncoding = Encoding . B.int32Dec
1298+ toEncoding = E.int32
12691299 {-# INLINE toEncoding #-}
12701300
1301+instance ToJSONKey Int32 where
1302+ toJSONKey = toJSONKeyTextEnc E.int32Text
1303+ {-# INLINE toJSONKey #-}
1304+
12711305
12721306instance ToJSON Int64 where
12731307 toJSON = Number . fromIntegral
12741308 {-# INLINE toJSON #-}
12751309
1276− toEncoding = Encoding . B.int64Dec
1310+ toEncoding = E.int64
12771311 {-# INLINE toEncoding #-}
12781312
1313+instance ToJSONKey Int64 where
1314+ toJSONKey = toJSONKeyTextEnc E.int64Text
1315+ {-# INLINE toJSONKey #-}
1316+
12791317
12801318instance ToJSON Word where
12811319 toJSON = Number . fromIntegral
12821320 {-# INLINE toJSON #-}
12831321
1284− toEncoding = Encoding . B.wordDec
1322+ toEncoding = E.word
12851323 {-# INLINE toEncoding #-}
12861324
1325+instance ToJSONKey Word where
1326+ toJSONKey = toJSONKeyTextEnc E.wordText
1327+ {-# INLINE toJSONKey #-}
1328+
12871329
12881330instance ToJSON Word8 where
12891331 toJSON = Number . fromIntegral
12901332 {-# INLINE toJSON #-}
12911333
1292− toEncoding = Encoding . B.word8Dec
1334+ toEncoding = E.word8
12931335 {-# INLINE toEncoding #-}
12941336
1337+instance ToJSONKey Word8 where
1338+ toJSONKey = toJSONKeyTextEnc E.word8Text
1339+ {-# INLINE toJSONKey #-}
1340+
12951341
12961342instance ToJSON Word16 where
12971343 toJSON = Number . fromIntegral
12981344 {-# INLINE toJSON #-}
12991345
1300− toEncoding = Encoding . B.word16Dec
1346+ toEncoding = E.word16
13011347 {-# INLINE toEncoding #-}
13021348
1349+instance ToJSONKey Word16 where
1350+ toJSONKey = toJSONKeyTextEnc E.word16Text
1351+ {-# INLINE toJSONKey #-}
1352+
13031353
13041354instance ToJSON Word32 where
13051355 toJSON = Number . fromIntegral
13061356 {-# INLINE toJSON #-}
13071357
1308− toEncoding = Encoding . B.word32Dec
1358+ toEncoding = E.word32
13091359 {-# INLINE toEncoding #-}
13101360
1361+instance ToJSONKey Word32 where
1362+ toJSONKey = toJSONKeyTextEnc E.word32Text
1363+ {-# INLINE toJSONKey #-}
1364+
1365+
13111366instance ToJSON Word64 where
13121367 toJSON = Number . fromIntegral
13131368 {-# INLINE toJSON #-}
13141369
1315− toEncoding = Encoding . B.word64Dec
1370+ toEncoding = E.word64
13161371 {-# INLINE toEncoding #-}
13171372
1373+instance ToJSONKey Word64 where
1374+ toJSONKey = toJSONKeyTextEnc E.word64Text
1375+ {-# INLINE toJSONKey #-}
1376+
1377+
13181378instance ToJSON Text where
13191379 toJSON = String
13201380 {-# INLINE toJSON #-}
13211381
1322− toEncoding = Encoding . EB.text
1382+ toEncoding = E.text
13231383 {-# INLINE toEncoding #-}
13241384
1385+instance ToJSONKey Text where
1386+ toJSONKey = ToJSONKeyText (id, toEncoding)
1387+ {-# INLINE toJSONKey #-}
1388+
13251389instance ToJSON LT.Text where
13261390 toJSON = String . LT.toStrict
13271391 {-# INLINE toJSON #-}
@@ -1365,9 +1429,12 @@ instance ToJSON Scientific where
13651429 toJSON = Number
13661430 {-# INLINE toJSON #-}
13671431
1368− toEncoding = Encoding . EB.number
1432+ toEncoding = E.scientific
13691433 {-# INLINE toEncoding #-}
13701434
1435+instance ToJSONKey Scientific where
1436+ toJSONKey = toJSONKeyTextEnc E.scientificText
1437+
13711438-------------------------------------------------------------------------------
13721439-- DList
13731440-------------------------------------------------------------------------------
@@ -1669,7 +1736,7 @@ instance ToJSON NominalDiffTime where
16691736 toJSON = Number . realToFrac
16701737 {-# INLINE toJSON #-}
16711738
1672− toEncoding = Encoding . EB.number . realToFrac
1739+ toEncoding = Encoding . EB.scientific . realToFrac
16731740 {-# INLINE toEncoding #-}
16741741
16751742-------------------------------------------------------------------------------
@@ -1755,21 +1822,14 @@ instance ToJSONKey b => ToJSONKey (Tagged a b) where
17551822-- Instances for converting t map keys
17561823-------------------------------------------------------------------------------
17571824
1758−instance ToJSONKey Text where
1759− toJSONKey = ToJSONKeyText (id,toEncoding)
1760−
1761−
1762−instance ToJSONKey Bool where
1763− toJSONKey = ToJSONKeyText
1764− ( (\x -> if x then "true" else "false")
1765− , (\x -> Encoding $ if x then "\"true\"" else "\"false\"")
1766− )
1767−
1768−instance ToJSONKey Int where
1769− toJSONKey = ToJSONKeyText
1770− ( LT.toStrict . LTB.toLazyText . LTBI.decimal
1771− , \x -> Encoding $ B.char7 '"' <> fromEncoding (toEncoding x) <> B.char7 '"'
1772− )
1825+-- | TODO: Move ToJSONKEyFunction to .Types(.Internal),
1826+-- export and document this function from there
1827+toJSONKeyTextEnc :: (a -> Encoding) -> ToJSONKeyFunction a
1828+toJSONKeyTextEnc e = ToJSONKeyText
1829+ -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
1830+ ( T.dropAround (== '"') . T.decodeLatin1 . L.toStrict . B.toLazyByteString . fromEncoding . e
1831+ , e
1832+ )
17731833
17741834instance (ToJSON a, ToJSON b) => ToJSONKey (a,b)
17751835instance (ToJSON a, ToJSON b, ToJSON c) => ToJSONKey (a,b,c)
tests/Properties.hs+8−4
@@ -15,6 +15,7 @@ import Data.DList (DList)
1515import Data.Hashable (Hashable)
1616import Data.HashMap.Strict (HashMap)
1717import Data.Map (Map)
18+import Data.Ratio (Ratio)
1819import Data.List.NonEmpty (NonEmpty)
1920import Data.Time (Day, LocalTime, NominalDiffTime, TimeOfDay, UTCTime,
2021 ZonedTime)
@@ -80,9 +81,6 @@ infix 4 ==~
8081x ==~ y =
8182 counterexample (show x ++ " /= " ++ show y) (x =~ y)
8283
83−roundTripApproxEq :: (ApproxEq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
84−roundTripApproxEq x y = roundTripEnc (==~) x y .&&. roundTripNoEnc (==~) x y
85−
8684toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
8785toFromJSON x = case ifromJSON (toJSON x) of
8886 IError path err -> failure "fromJSON" (formatError path err) x
@@ -152,7 +150,7 @@ tests = testGroup "properties" [
152150 , testProperty "Text" $ roundTripEq T.empty
153151 , testProperty "Foo" $ roundTripEq (undefined :: Foo)
154152 , testProperty "Day" $ roundTripEq (undefined :: Day)
155− , testProperty "DotNetTime" $ roundTripApproxEq (undefined :: DotNetTime)
153+ , testProperty "DotNetTime" $ roundTripEq (undefined :: Approx DotNetTime)
156154 , testProperty "LocalTime" $ roundTripEq (undefined :: LocalTime)
157155 , testProperty "TimeOfDay" $ roundTripEq (undefined :: TimeOfDay)
158156 , testProperty "UTCTime" $ roundTripEq (undefined :: UTCTime)
@@ -165,6 +163,8 @@ tests = testGroup "properties" [
165163 , testProperty "Const" $ roundTripEq (undefined :: Const Int Char)
166164 , testProperty "DList" $ roundTripEq (undefined :: DList Int)
167165 , testProperty "Seq" $ roundTripEq (undefined :: Seq Int)
166+ , testProperty "Rational" $ roundTripEq (undefined :: Rational)
167+ , testProperty "Ratio Int" $ roundTripEq (undefined :: Ratio Int)
168168 , testGroup "ghcGenerics" [
169169 testProperty "OneConstructor" $ roundTripEq OneConstructor
170170 , testProperty "Product2" $ roundTripEq (undefined :: Product2 Int Bool)
@@ -179,6 +179,10 @@ tests = testGroup "properties" [
179179 , testProperty "Int" $ roundTripKey (undefined :: Int)
180180 , testProperty "[Text]" $ roundTripKey (undefined :: [T.Text])
181181 , testProperty "(Int,Char)" $ roundTripKey (undefined :: (Int,Char))
182+ , testProperty "Integer" $ roundTripKey (undefined :: Integer)
183+ , testProperty "Natural" $ roundTripKey (undefined :: Natural)
184+ , testProperty "Float" $ roundTripKey (undefined :: Float)
185+ , testProperty "Double" $ roundTripKey (undefined :: Double)
182186 ]
183187 , testGroup "toFromJSON" [
184188 testProperty "Integer" (toFromJSON :: Integer -> Property)
tests/UnitTests.hs+8
@@ -37,6 +37,7 @@ import qualified Data.Text.Lazy.Encoding as LT
3737
3838import qualified Data.Sequence as Seq
3939import qualified Data.DList as DList
40+import qualified Data.HashMap.Strict as HM
4041
4142tests :: Test
4243tests = testGroup "unit" [
@@ -246,6 +247,13 @@ jsonEncoding = [
246247 , assertEqual "Seq" "[1,2,3]" $ encode (Seq.fromList [1, 2, 3] :: Seq.Seq Int)
247248 , assertEqual "DList" "[1,2,3]" $ encode (DList.fromList [1, 2, 3] :: DList.DList Int)
248249 , assertEqual "()" "[]" $ encode ()
250+ , assertEqual "HashMap Int Int" "{\"0\":1,\"2\":3}" $ encode (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
251+ , assertEqual "nan :: Double" "null" $ encode (0/0 :: Double)
252+ , assertEqual "inf :: Double" "null" $ encode (1/0 :: Double)
253+ -- Three separate cases, as ordering in HashMap is not defined
254+ , assertEqual "HashMap Float Int, NaN" "{\"NaN\":1}" $ encode (HM.singleton (0/0) 1 :: HM.HashMap Float Int)
255+ , assertEqual "HashMap Float Int, Infinity" "{\"Infinity\":1}" $ encode (HM.singleton (1/0) 1 :: HM.HashMap Float Int)
256+ , assertEqual "HashMap Float Int, +Infinity" "{\"-Infinity\":1}" $ encode (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)
249257 ]
250258
251259jsonDecoding :: [Assertion]