on master help sign in
Merge pull request #421 from phadej/time-keys

Data.Time keys, and almost done Encoding refactoring

commitf81a260dc168f3cfe1d598b97d0b9af23d3fd8a8 authorAdam Bergmark <adam@bergmark.nl> dateThu 9 Jun 2016, 20:23 (10 years ago) parentad507941874b37
7 files changed+143−56
Data/Aeson/Encoding.hs+13 Data/Aeson/Encoding/Internal.hs+54−1 Data/Aeson/Types/FromJSON.hs+20 Data/Aeson/Types/ToJSON.hs+43−54 aeson.cabal+3 tests/Instances.hs+3 tests/Properties.hs+7−1
Data/Aeson/Encoding.hs+13
@@ -6,12 +6,15 @@ module Data.Aeson.Encoding
66 , unsafeToEncoding
77 , Series
88 , pairs
9+ , pair
910 -- * Predicates
1011 , nullEncoding
1112 -- * Encoding constructors
1213 , emptyArray_
1314 , emptyObject_
1415 , text
16+ , lazyText
17+ , string
1518 , list
1619 , dict
1720 , null_
@@ -25,6 +28,16 @@ module Data.Aeson.Encoding
2528 , int8Text, int16Text, int32Text, int64Text, intText
2629 , word8Text, word16Text, word32Text, word64Text, wordText
2730 , integerText, floatText, doubleText, scientificText
31+
32+ -- ** Time
33+ , day
34+ , localTime
35+ , utcTime
36+ , timeOfDay
37+ , zonedTime
38+
39+ -- ** value
40+ , value
2841 ) where
2942
3043import Data.Aeson.Encoding.Internal
Data/Aeson/Encoding/Internal.hs+54−1
@@ -1,7 +1,7 @@
11{-# LANGUAGE DeriveDataTypeable #-}
22{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3−{-# LANGUAGE RankNTypes #-}
43{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RankNTypes #-}
55module Data.Aeson.Encoding.Internal
66 (
77 -- * Encoding
@@ -9,6 +9,7 @@ module Data.Aeson.Encoding.Internal
99 , unsafeToEncoding
1010 , Series (..) -- TODO: don't export constructor
1111 , pairs
12+ , pair
1213 -- * Predicates
1314 , nullEncoding
1415 -- * Encoding constructors
@@ -19,6 +20,8 @@ module Data.Aeson.Encoding.Internal
1920 , null_
2021 , bool
2122 , text
23+ , lazyText
24+ , string
2225 , list
2326 , dict
2427 , tuple
@@ -31,6 +34,14 @@ module Data.Aeson.Encoding.Internal
3134 , int8Text, int16Text, int32Text, int64Text, intText
3235 , word8Text, word16Text, word32Text, word64Text, wordText
3336 , integerText, floatText, doubleText, scientificText
37+ -- ** Time
38+ , day
39+ , localTime
40+ , utcTime
41+ , timeOfDay
42+ , zonedTime
43+ -- ** value
44+ , value
3445 -- ** JSON tokens
3546 , comma, colon, openBracket, closeBracket, openCurly, closeCurly
3647 ) where
@@ -44,12 +55,17 @@ import Data.Int
4455import Data.Scientific (Scientific)
4556import Data.Semigroup (Semigroup ((<>)))
4657import Data.Text (Text)
58+import Data.Time (Day, LocalTime, TimeOfDay, UTCTime,
59+ ZonedTime)
4760import Data.Typeable (Typeable)
4861import Data.Word
4962
63+import Data.Aeson.Types.Internal (Value)
64+
5065import qualified Data.Aeson.Encode.Builder as EB
5166import qualified Data.ByteString.Builder as B
5267import qualified Data.ByteString.Lazy as BSL
68+import qualified Data.Text.Lazy as LT
5369
5470-- | An encoding of a JSON value.
5571newtype Encoding = Encoding {
@@ -89,6 +105,9 @@ data Series = Empty
89105 | Value Encoding
90106 deriving (Typeable)
91107
108+pair :: Text -> Encoding -> Series
109+pair name val = Value $ text name <> colon <> val
110+
92111instance Semigroup Series where
93112 Empty <> a = a
94113 Value a <> b =
@@ -174,6 +193,14 @@ tuple b = Encoding (char7 '[' <> fromEncoding b <> char7 ']')
174193text :: Text -> Encoding
175194text = Encoding . EB.text
176195
196+lazyText :: LT.Text -> Encoding
197+lazyText t = Encoding $
198+ B.char7 '"' <>
199+ LT.foldrChunks (\x xs -> EB.unquoted x <> xs) (B.char7 '"') t
200+
201+string :: String -> Encoding
202+string = Encoding . EB.string
203+
177204-------------------------------------------------------------------------------
178205-- chars
179206-------------------------------------------------------------------------------
@@ -284,3 +311,29 @@ doubleText = Encoding . EB.quote . B.doubleDec
284311
285312scientificText :: Scientific -> Encoding
286313scientificText = Encoding . EB.quote . EB.scientific
314+
315+-------------------------------------------------------------------------------
316+-- time
317+-------------------------------------------------------------------------------
318+
319+day :: Day -> Encoding
320+day = Encoding . EB.quote . EB.day
321+
322+localTime :: LocalTime -> Encoding
323+localTime = Encoding . EB.quote . EB.localTime
324+
325+utcTime :: UTCTime -> Encoding
326+utcTime = Encoding . EB.quote . EB.utcTime
327+
328+timeOfDay :: TimeOfDay -> Encoding
329+timeOfDay = Encoding . EB.quote . EB.timeOfDay
330+
331+zonedTime :: ZonedTime -> Encoding
332+zonedTime = Encoding . EB.quote . EB.zonedTime
333+
334+-------------------------------------------------------------------------------
335+-- Value
336+-------------------------------------------------------------------------------
337+
338+value :: Value -> Encoding
339+value = Encoding . EB.encodeToBuilder
Data/Aeson/Types/FromJSON.hs+20
@@ -1382,18 +1382,38 @@ instance FromJSON DotNetTime where
13821382instance FromJSON Day where
13831383 parseJSON = withText "Day" (Time.run Time.day)
13841384
1385+instance FromJSONKey Day where
1386+ fromJSONKey = FromJSONKeyTextParser (Time.run Time.day)
1387+
1388+
13851389instance FromJSON TimeOfDay where
13861390 parseJSON = withText "TimeOfDay" (Time.run Time.timeOfDay)
13871391
1392+instance FromJSONKey TimeOfDay where
1393+ fromJSONKey = FromJSONKeyTextParser (Time.run Time.timeOfDay)
1394+
1395+
13881396instance FromJSON LocalTime where
13891397 parseJSON = withText "LocalTime" (Time.run Time.localTime)
13901398
1399+instance FromJSONKey LocalTime where
1400+ fromJSONKey = FromJSONKeyTextParser (Time.run Time.localTime)
1401+
1402+
13911403instance FromJSON ZonedTime where
13921404 parseJSON = withText "ZonedTime" (Time.run Time.zonedTime)
13931405
1406+instance FromJSONKey ZonedTime where
1407+ fromJSONKey = FromJSONKeyTextParser (Time.run Time.zonedTime)
1408+
1409+
13941410instance FromJSON UTCTime where
13951411 parseJSON = withText "UTCTime" (Time.run Time.utcTime)
13961412
1413+instance FromJSONKey UTCTime where
1414+ fromJSONKey = FromJSONKeyTextParser (Time.run Time.utcTime)
1415+
1416+
13971417-- | /WARNING:/ Only parse lengths of time from trusted input
13981418-- since an attacker could easily fill up the memory of the target
13991419-- system by specifying a scientific number with a big exponent like
Data/Aeson/Types/ToJSON.hs+43−54
@@ -61,7 +61,7 @@ import Data.Aeson.Functions (mapHashKeyVal, mapKeyVal)
6161import Data.Aeson.Types.Generic
6262import Data.Aeson.Types.Internal
6363
64−import qualified Data.Aeson.Encode.Builder as EB
64+-- We need internal here for generic deriving
6565import qualified Data.Aeson.Encoding.Internal as E
6666
6767import Control.Applicative (Const (..))
@@ -92,7 +92,7 @@ import Foreign.Storable (Storable)
9292import GHC.Generics
9393import Numeric.Natural (Natural)
9494
95−import qualified Data.ByteString.Builder as B
95+import qualified Data.ByteString.Builder as B (toLazyByteString)
9696import qualified Data.ByteString.Lazy as L
9797import qualified Data.DList as DList
9898import qualified Data.HashMap.Strict as H
@@ -115,9 +115,6 @@ import qualified Data.Vector.Primitive as VP
115115import qualified Data.Vector.Storable as VS
116116import qualified Data.Vector.Unboxed as VU
117117
118−builder :: ToJSON a => a -> B.Builder
119−builder = fromEncoding . toEncoding
120−
121118toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
122119toJSONPair keySerialiser valSerializer (a, b) = Array $ V.create $ do
123120 mv <- VM.unsafeNew 2
@@ -278,7 +275,7 @@ class ToJSON a where
278275 -- @
279276
280277 toEncoding :: a -> Encoding
281− toEncoding = E.Encoding . EB.encodeToBuilder . toJSON
278+ toEncoding = E.value . toJSON
282279 {-# INLINE toEncoding #-}
283280
284281 toJSONList :: [a] -> Value
@@ -299,7 +296,7 @@ class KeyValue kv where
299296 infixr 8 .=
300297
301298instance KeyValue Series where
302− name .= value = E.Value $ E.text name <> E.colon <> toEncoding value
299+ name .= value = E.pair name (toEncoding value)
303300 {-# INLINE (.=) #-}
304301
305302instance KeyValue Pair where
@@ -1192,7 +1189,7 @@ instance ToJSON1 Maybe where
11921189 {-# INLINE liftToJSON #-}
11931190
11941191 liftToEncoding t _ (Just a) = t a
1195− liftToEncoding _ _ Nothing = Encoding EB.null_
1192+ liftToEncoding _ _ Nothing = E.null_
11961193 {-# INLINE liftToEncoding #-}
11971194
11981195instance (ToJSON a) => ToJSON (Maybe a) where
@@ -1208,15 +1205,9 @@ instance ToJSON2 Either where
12081205 liftToJSON2 _toA _ toB _ (Right b) = Object $ H.singleton right (toB b)
12091206 {-# INLINE liftToJSON2 #-}
12101207
1211− liftToEncoding2 toA _ _toB _ (Left a) =
1212− Encoding (B.shortByteString "{\"Left\":")
1213− <> toA a
1214− <> Encoding (B.char7 '}')
1208+ liftToEncoding2 toA _ _toB _ (Left a) = E.pairs $ E.pair "Left" $ toA a
12151209
1216− liftToEncoding2 _toA _ toB _ (Right b) =
1217− Encoding (B.shortByteString "{\"Right\":")
1218− <> toB b
1219− <> Encoding (B.char7 '}')
1210+ liftToEncoding2 _toA _ toB _ (Right b) = E.pairs $ E.pair "Right" $ toB b
12201211 {-# INLINE liftToEncoding2 #-}
12211212
12221213instance (ToJSON a) => ToJSON1 (Either a) where
@@ -1277,10 +1268,10 @@ instance ToJSON Char where
12771268 toJSONList = String . T.pack
12781269 {-# INLINE toJSONList #-}
12791270
1280− toEncoding = Encoding . EB.string . (:[])
1271+ toEncoding = E.string . (:[])
12811272 {-# INLINE toEncoding #-}
12821273
1283− toEncodingList = Encoding . EB.string
1274+ toEncodingList = E.string
12841275 {-# INLINE toEncodingList #-}
12851276
12861277
@@ -1501,9 +1492,7 @@ instance ToJSON LT.Text where
15011492 toJSON = String . LT.toStrict
15021493 {-# INLINE toJSON #-}
15031494
1504− toEncoding t = Encoding $
1505− B.char7 '"' <>
1506− LT.foldrChunks (\x xs -> EB.unquoted x <> xs) (B.char7 '"') t
1495+ toEncoding = E.lazyText
15071496 {-# INLINE toEncoding #-}
15081497
15091498instance ToJSON Version where
@@ -1513,7 +1502,6 @@ instance ToJSON Version where
15131502 toEncoding = toEncoding . showVersion
15141503 {-# INLINE toEncoding #-}
15151504
1516−
15171505-------------------------------------------------------------------------------
15181506-- semigroups NonEmpty
15191507-------------------------------------------------------------------------------
@@ -1686,13 +1674,7 @@ instance ToJSON1 Vector where
16861674 liftToJSON t _ = Array . V.map t
16871675 {-# INLINE liftToJSON #-}
16881676
1689− liftToEncoding t _ xs
1690− | V.null xs = emptyArray_
1691− | otherwise = Encoding $
1692− B.char7 '[' <> fromEncoding (t (V.unsafeHead xs)) <>
1693− V.foldr go (B.char7 ']') (V.unsafeTail xs)
1694− where
1695− go v b = B.char7 ',' <> fromEncoding (t v) <> b
1677+ liftToEncoding t _ = listEncoding t . V.toList
16961678 {-# INLINE liftToEncoding #-}
16971679
16981680instance (ToJSON a) => ToJSON (Vector a) where
@@ -1703,12 +1685,7 @@ instance (ToJSON a) => ToJSON (Vector a) where
17031685 {-# INLINE toEncoding #-}
17041686
17051687encodeVector :: (ToJSON a, VG.Vector v a) => v a -> Encoding
1706−encodeVector xs
1707− | VG.null xs = emptyArray_
1708− | otherwise = Encoding $
1709− B.char7 '[' <> builder (VG.unsafeHead xs) <>
1710− VG.foldr go (B.char7 ']') (VG.unsafeTail xs)
1711− where go v b = B.char7 ',' <> builder v <> b
1688+encodeVector = listEncoding toEncoding . VG.toList
17121689{-# INLINE encodeVector #-}
17131690
17141691vectorToJSON :: (VG.Vector v a, ToJSON a) => v a -> Value
@@ -1787,7 +1764,7 @@ instance ToJSON Value where
17871764 toJSON a = a
17881765 {-# INLINE toJSON #-}
17891766
1790− toEncoding = Encoding . EB.encodeToBuilder
1767+ toEncoding = E.value
17911768 {-# INLINE toEncoding #-}
17921769
17931770instance ToJSON DotNetTime where
@@ -1799,55 +1776,67 @@ dotNetTime :: DotNetTime -> String
17991776dotNetTime (DotNetTime t) = secs ++ formatMillis t ++ ")/"
18001777 where secs = formatTime defaultTimeLocale "/Date(%s" t
18011778
1779+formatMillis :: (FormatTime t) => t -> String
1780+formatMillis = take 3 . formatTime defaultTimeLocale "%q"
1781+
18021782-------------------------------------------------------------------------------
18031783-- time
18041784-------------------------------------------------------------------------------
18051785
18061786instance ToJSON Day where
1807− toJSON = stringEncoding
1808− toEncoding z = Encoding (EB.quote $ EB.day z)
1787+ toJSON = stringEncoding . E.day
1788+ toEncoding = E.day
1789+
1790+instance ToJSONKey Day where
1791+ toJSONKey = toJSONKeyTextEnc E.day
18091792
18101793
18111794instance ToJSON TimeOfDay where
1812− toJSON = stringEncoding
1813− toEncoding z = Encoding (EB.quote $ EB.timeOfDay z)
1795+ toJSON = stringEncoding . E.timeOfDay
1796+ toEncoding = E.timeOfDay
1797+
1798+instance ToJSONKey TimeOfDay where
1799+ toJSONKey = toJSONKeyTextEnc E.timeOfDay
18141800
18151801
18161802instance ToJSON LocalTime where
1817− toJSON = stringEncoding
1818− toEncoding z = Encoding (EB.quote $ EB.localTime z)
1803+ toJSON = stringEncoding . E.localTime
1804+ toEncoding = E.localTime
1805+
1806+instance ToJSONKey LocalTime where
1807+ toJSONKey = toJSONKeyTextEnc E.localTime
1808+
18191809
18201810instance ToJSON ZonedTime where
1821− toJSON = stringEncoding
1811+ toJSON = stringEncoding . E.zonedTime
1812+ toEncoding = E.zonedTime
18221813
1823− toEncoding z = Encoding (EB.quote $ EB.zonedTime z)
1814+instance ToJSONKey ZonedTime where
1815+ toJSONKey = toJSONKeyTextEnc E.zonedTime
18241816
1825−formatMillis :: (FormatTime t) => t -> String
1826−formatMillis = take 3 . formatTime defaultTimeLocale "%q"
18271817
18281818instance ToJSON UTCTime where
1829− toJSON = stringEncoding
1819+ toJSON = stringEncoding . E.utcTime
1820+ toEncoding = E.utcTime
18301821
1831− toEncoding t = Encoding (EB.quote $ EB.utcTime t)
1822+instance ToJSONKey UTCTime where
1823+ toJSONKey = toJSONKeyTextEnc E.utcTime
18321824
18331825-- | Encode something t a JSON string.
1834−--
1835−-- TODO: remove me, this is a hack
1836−stringEncoding :: (ToJSON a) => a -> Value
1826+stringEncoding :: Encoding -> Value
18371827stringEncoding = String
18381828 . T.dropAround (== '"')
18391829 . T.decodeLatin1
18401830 . L.toStrict
18411831 . B.toLazyByteString
18421832 . fromEncoding
1843− . toEncoding
18441833{-# INLINE stringEncoding #-}
18451834
18461835instance ToJSON NominalDiffTime where
18471836 toJSON = Number . realToFrac
18481837 {-# INLINE toJSON #-}
18491838
1850− toEncoding = Encoding . EB.scientific . realToFrac
1839+ toEncoding = E.scientific . realToFrac
18511840 {-# INLINE toEncoding #-}
18521841
18531842-------------------------------------------------------------------------------
@@ -1906,7 +1895,7 @@ instance ToJSON (Proxy a) where
19061895 toJSON _ = Null
19071896 {-# INLINE toJSON #-}
19081897
1909− toEncoding _ = Encoding EB.null_
1898+ toEncoding _ = E.null_
19101899 {-# INLINE toEncoding #-}
19111900
19121901
aeson.cabal+3
@@ -174,6 +174,9 @@ test-suite tests
174174 if !impl(ghc >= 7.10)
175175 build-depends: nats >=1 && <1.2
176176
177+ if impl(ghc >= 7.8)
178+ build-depends: hashable-time >= 0.2 && <0.3
179+
177180source-repository head
178181 type: git
179182 location: git://github.com/bos/aeson.git
tests/Instances.hs+3
@@ -28,6 +28,9 @@ import qualified Data.DList as DList
2828
2929import Data.Orphans ()
3030import Test.QuickCheck.Instances ()
31+#if MIN_VERSION_base(4,7,0)
32+import Data.Hashable.Time ()
33+#endif
3134
3235#if !MIN_VERSION_base(4,8,0) && !MIN_VERSION_QuickCheck(2,8,3)
3336import Numeric.Natural
tests/Properties.hs+7−1
@@ -1,4 +1,4 @@
1−{-# LANGUAGE OverloadedStrings, RecordWildCards, ScopedTypeVariables #-}
1+{-# LANGUAGE CPP, OverloadedStrings, ScopedTypeVariables #-}
22
33module Properties where
44
@@ -186,6 +186,12 @@ tests = testGroup "properties" [
186186 , testProperty "Natural" $ roundTripKey (undefined :: Natural)
187187 , testProperty "Float" $ roundTripKey (undefined :: Float)
188188 , testProperty "Double" $ roundTripKey (undefined :: Double)
189+#if MIN_VERSION_base(4,7,0)
190+ , testProperty "Day" $ roundTripKey (undefined :: Day)
191+ , testProperty "LocalTime" $ roundTripKey (undefined :: LocalTime)
192+ , testProperty "TimeOfDay" $ roundTripKey (undefined :: TimeOfDay)
193+ , testProperty "UTCTime" $ roundTripKey (undefined :: UTCTime)
194+#endif
189195 ]
190196 , testGroup "toFromJSON" [
191197 testProperty "Integer" (toFromJSON :: Integer -> Property)