on master help sign in
Refactor ToJSONKeyFunction, add some docs
commite056318deb9ac4768e52cc5a2fe8e6991e73c1bb authorOleg Grenrus <oleg.grenrus@iki.fi> dateThu 9 Jun 2016, 11:46 (10 years ago) parentc9dce67
5 files changed+61−37
Data/Aeson/Types.hs+3 Data/Aeson/Types/Class.hs+1 Data/Aeson/Types/FromJSON.hs+16−6 Data/Aeson/Types/ToJSON.hs+41−30 tests/UnitTests.hs−1
Data/Aeson/Types.hs+3
@@ -40,10 +40,13 @@ module Data.Aeson.Types
4040 -- ** Keys for maps
4141 , ToJSONKey(..)
4242 , ToJSONKeyFunction(..)
43+ , toJSONKeyText
44+ , contramapToJSONKeyFunction
4345 , FromJSONKey(..)
4446 , FromJSONKeyFunction(..)
4547 , fromJSONKeyCoerce
4648 , coerceFromJSONKeyFunction
49+ , mapFromJSONKeyFunction
4750
4851 -- ** Liftings to unary and binary type constructors
4952 , FromJSON1(..)
Data/Aeson/Types/Class.hs+1
@@ -51,6 +51,7 @@ module Data.Aeson.Types.Class
5151 -- * Classes and types for map keys
5252 , ToJSONKey(..)
5353 , ToJSONKeyFunction(..)
54+ , toJSONKeyText
5455 , contramapToJSONKeyFunction
5556 , FromJSONKey(..)
5657 , FromJSONKeyFunction(..)
Data/Aeson/Types/FromJSON.hs+16−6
@@ -127,9 +127,8 @@ import qualified Data.Vector.Unboxed as VU
127127
128128import Unsafe.Coerce (unsafeCoerce)
129129
130−-- Coercible derivations aren't as powerful on GHC 7.8, though supported.
131130#ifndef HAS_COERCIBLE
132−#define HAS_COERCIBLE (__GLASGOW_HASKELL__ >= 709)
131+#define HAS_COERCIBLE (__GLASGOW_HASKELL__ >= 707)
133132#endif
134133
135134#if HAS_COERCIBLE
@@ -325,6 +324,7 @@ class FromJSONKey a where
325324 fromJSONKey :: FromJSONKeyFunction a
326325 default fromJSONKey :: FromJSON a => FromJSONKeyFunction a
327326 fromJSONKey = FromJSONKeyValue parseJSON
327+
328328 fromJSONKeyList :: FromJSONKeyFunction [a]
329329 default fromJSONKeyList :: FromJSON a => FromJSONKeyFunction [a]
330330 fromJSONKeyList = FromJSONKeyValue parseJSON
@@ -343,10 +343,10 @@ data CoerceText a where
343343#endif
344344
345345data FromJSONKeyFunction a
346− = FromJSONKeyCoerce (CoerceText a)
347− | FromJSONKeyText (Text -> a)
348− | FromJSONKeyTextParser (Text -> Parser a)
349− | FromJSONKeyValue (Value -> Parser a)
346+ = FromJSONKeyCoerce !(CoerceText a)
347+ | FromJSONKeyText !(Text -> a)
348+ | FromJSONKeyTextParser !(Text -> Parser a)
349+ | FromJSONKeyValue !(Value -> Parser a)
350350
351351instance Functor FromJSONKeyFunction where
352352 fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce')
@@ -357,6 +357,8 @@ instance Functor FromJSONKeyFunction where
357357-- | Construct 'FromJSONKeyFunction' for types coercible from 'Text'. This
358358-- conversion is still unsafe, as 'Hashable' and 'Eq' instances of @a@ should be
359359-- compatible with 'Text' i.e. hash values be equal for wrapped values as well.
360+--
361+-- On pre GHC 7.8 this is unconstrainted function.
360362fromJSONKeyCoerce ::
361363#if HAS_COERCIBLE
362364 Coercible Text a =>
@@ -364,15 +366,22 @@ fromJSONKeyCoerce ::
364366 FromJSONKeyFunction a
365367fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
366368
369+-- | Semantically the same as @coerceFromJSONKeyFunction = fmap coerce = coerce@.
370+--
371+-- See note on 'fromJSONKeyCoerce'.
367372coerceFromJSONKeyFunction ::
368373#if HAS_COERCIBLE
369374 Coercible a b =>
370375#endif
371376 FromJSONKeyFunction a -> FromJSONKeyFunction b
377+#if HAS_COERCIBLE
378+coerceFromJSONKeyFunction = coerce
379+#else
372380coerceFromJSONKeyFunction (FromJSONKeyCoerce CoerceText) = FromJSONKeyCoerce CoerceText
373381coerceFromJSONKeyFunction (FromJSONKeyText f) = FromJSONKeyText (coerce' . f)
374382coerceFromJSONKeyFunction (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap coerce' . f)
375383coerceFromJSONKeyFunction (FromJSONKeyValue f) = FromJSONKeyValue (fmap coerce' . f)
384+#endif
376385
377386{-# RULES
378387 "FromJSONKeyCoerce: fmap id" forall (x :: FromJSONKeyFunction a).
@@ -385,6 +394,7 @@ coerceFromJSONKeyFunction (FromJSONKeyValue f) = FromJSONKeyValue (fma
385394 #-}
386395#endif
387396
397+-- | Same as 'fmap'. Provided for the consistency with 'ToJSONKeyFunction'.
388398mapFromJSONKeyFunction :: (a -> b) -> FromJSONKeyFunction a -> FromJSONKeyFunction b
389399mapFromJSONKeyFunction = fmap
390400
Data/Aeson/Types/ToJSON.hs+41−30
@@ -43,6 +43,7 @@ module Data.Aeson.Types.ToJSON (
4343 -- * Classes and types for map keys
4444 , ToJSONKey(..)
4545 , ToJSONKeyFunction(..)
46+ , toJSONKeyText
4647 , contramapToJSONKeyFunction
4748 -- * Object key-value pairs
4849 , KeyValue(..)
@@ -413,22 +414,44 @@ class ToJSONKey a where
413414 -- container.
414415 toJSONKey :: ToJSONKeyFunction a
415416 default toJSONKey :: ToJSON a => ToJSONKeyFunction a
416− toJSONKey = ToJSONKeyValue (toJSON, toEncoding)
417+ toJSONKey = ToJSONKeyValue toJSON toEncoding
418+
417419 -- | This is similar in spirit to the 'showsList' method of 'Show'.
418420 -- It makes it possible to give 'String' keys special treatment
419421 -- without using @OverlappingInstances@.
420422 toJSONKeyList :: ToJSONKeyFunction [a]
421423 default toJSONKeyList :: ToJSON a => ToJSONKeyFunction [a]
422− toJSONKeyList = ToJSONKeyValue (toJSON, toEncoding)
424+ toJSONKeyList = ToJSONKeyValue toJSON toEncoding
423425
424426data ToJSONKeyFunction a
425− = ToJSONKeyText (a -> Text, a -> Encoding' Text) -- ^ key is encoded to string, produces object
426− | ToJSONKeyValue (a -> Value, a -> Encoding) -- ^ key is encoded to value, produces array
427+ = ToJSONKeyText !(a -> Text) !(a -> Encoding' Text)
428+ -- ^ key is encoded to string, produces object
429+ | ToJSONKeyValue !(a -> Value) !(a -> Encoding)
430+ -- ^ key is encoded to value, produces array
431+
432+-- | Helper for creating textual keys.
433+--
434+-- @
435+-- instance 'ToJSONKey' MyKey where
436+-- 'toJSONKey' = 'toJSONKeyText' myKeyToText
437+-- where
438+-- myKeyToText = Text.pack . show -- or showt from text-show
439+-- @
440+toJSONKeyText :: (a -> Text) -> ToJSONKeyFunction a
441+toJSONKeyText f = ToJSONKeyText f (E.text . f)
427442
443+-- | TODO: should this be exported?
444+toJSONKeyTextEnc :: (a -> Encoding' Text) -> ToJSONKeyFunction a
445+toJSONKeyTextEnc e = ToJSONKeyText tot e
446+ where
447+ -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
448+ tot = T.dropAround (== '"') . T.decodeLatin1 . L.toStrict . E.encodingToLazyByteString . e
449+
450+-- | Contravariant map, as 'ToJSONKeyFunction' is a contravariant functor.
428451contramapToJSONKeyFunction :: (b -> a) -> ToJSONKeyFunction a -> ToJSONKeyFunction b
429452contramapToJSONKeyFunction h x = case x of
430− ToJSONKeyText (f,g) -> ToJSONKeyText (f . h, g . h)
431− ToJSONKeyValue (f,g) -> ToJSONKeyValue (f . h, g . h)
453+ ToJSONKeyText f g -> ToJSONKeyText (f . h) (g . h)
454+ ToJSONKeyValue f g -> ToJSONKeyValue (f . h) (g . h)
432455
433456-------------------------------------------------------------------------------
434457-- Lifings of FromJSON and ToJSON to unary and binary type constructors
@@ -1231,10 +1254,7 @@ instance ToJSON Bool where
12311254 {-# INLINE toEncoding #-}
12321255
12331256instance ToJSONKey Bool where
1234− toJSONKey = ToJSONKeyText
1235− ( (\x -> if x then "true" else "false")
1236− , (\x -> E.text $ if x then "true"else "false")
1237− )
1257+ toJSONKey = toJSONKeyText $ \x -> if x then "true" else "false"
12381258
12391259
12401260instance ToJSON Ordering where
@@ -1479,7 +1499,7 @@ instance ToJSON Text where
14791499 {-# INLINE toEncoding #-}
14801500
14811501instance ToJSONKey Text where
1482− toJSONKey = ToJSONKeyText (id, E.text)
1502+ toJSONKey = toJSONKeyText id
14831503 {-# INLINE toJSONKey #-}
14841504
14851505instance ToJSON LT.Text where
@@ -1611,14 +1631,14 @@ instance ToJSON a => ToJSON (IntMap.IntMap a) where
16111631
16121632instance ToJSONKey k => ToJSON1 (M.Map k) where
16131633 liftToJSON g _ = case toJSONKey of
1614− ToJSONKeyText (f,_) -> Object . mapHashKeyVal f g
1615− ToJSONKeyValue (f,_) -> Array . V.fromList . map (toJSONPair f g) . M.toList
1634+ ToJSONKeyText f _ -> Object . mapHashKeyVal f g
1635+ ToJSONKeyValue f _ -> Array . V.fromList . map (toJSONPair f g) . M.toList
16161636 {-# INLINE liftToJSON #-}
16171637
16181638 -- liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> M.Map k a -> Encoding
16191639 liftToEncoding g _ = case toJSONKey of
1620− ToJSONKeyText (_,f) -> dict f g M.foldrWithKey
1621− ToJSONKeyValue (_,f) -> listEncoding (pairEncoding f) . M.toList
1640+ ToJSONKeyText _ f -> dict f g M.foldrWithKey
1641+ ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . M.toList
16221642 where
16231643 -- pairEncoding :: (k -> Encoding) -> (k, a) -> Encoding
16241644 pairEncoding f (a, b) = tuple $ f a >*< g b
@@ -1730,14 +1750,14 @@ instance (ToJSON a) => ToJSON (HashSet.HashSet a) where
17301750
17311751instance ToJSONKey k => ToJSON1 (H.HashMap k) where
17321752 liftToJSON g _ = case toJSONKey of
1733− ToJSONKeyText (f,_) -> Object . mapKeyVal f g
1734− ToJSONKeyValue (f,_) -> Array . V.fromList . map (toJSONPair f g) . H.toList
1753+ ToJSONKeyText f _ -> Object . mapKeyVal f g
1754+ ToJSONKeyValue f _ -> Array . V.fromList . map (toJSONPair f g) . H.toList
17351755 {-# INLINE liftToJSON #-}
17361756
17371757 -- liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> H.HashMap k a -> Encoding
17381758 liftToEncoding g _ = case toJSONKey of
1739− ToJSONKeyText (_,f) -> dict f g H.foldrWithKey
1740− ToJSONKeyValue (_,f) -> listEncoding (pairEncoding f) . H.toList
1759+ ToJSONKeyText _ f -> dict f g H.foldrWithKey
1760+ ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . H.toList
17411761 where
17421762 -- pairEncoding :: (k -> Encoding) -> (k, a) -> Encoding
17431763 pairEncoding f (a, b) = tuple $ f a >*< g b
@@ -1915,22 +1935,13 @@ instance ToJSONKey b => ToJSONKey (Tagged a b) where
19151935-- Instances for converting t map keys
19161936-------------------------------------------------------------------------------
19171937
1918−-- | TODO: Move ToJSONKEyFunction to .Types(.Internal),
1919−-- export and document this function from there
1920−toJSONKeyTextEnc :: (a -> Encoding' Text) -> ToJSONKeyFunction a
1921−toJSONKeyTextEnc e = ToJSONKeyText
1922− -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
1923− ( T.dropAround (== '"') . T.decodeLatin1 . L.toStrict . E.encodingToLazyByteString . e
1924− , e
1925− )
1926−
19271938instance (ToJSON a, ToJSON b) => ToJSONKey (a,b)
19281939instance (ToJSON a, ToJSON b, ToJSON c) => ToJSONKey (a,b,c)
19291940instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSONKey (a,b,c,d)
19301941
19311942instance ToJSONKey Char where
1932− toJSONKey = ToJSONKeyText (T.singleton, E.string . (:[]))
1933− toJSONKeyList = ToJSONKeyText (T.pack, E.text . T.pack)
1943+ toJSONKey = ToJSONKeyText T.singleton (E.string . (:[]))
1944+ toJSONKeyList = toJSONKeyText T.pack
19341945
19351946instance (ToJSONKey a, ToJSON a) => ToJSONKey [a] where
19361947 toJSONKey = toJSONKeyList
tests/UnitTests.hs−1
@@ -322,7 +322,6 @@ fromJSONKeyAssertions =
322322 , assertIsCoerce "Tagged Int Text" (fromJSONKey :: FromJSONKeyFunction (Tagged Int Text))
323323 , assertIsCoerce "MyText" (fromJSONKey :: FromJSONKeyFunction MyText)
324324
325−-- Why this doesn't work on older GHC?
326325#if __GLASGOW_HASKELL__ >= 710
327326 , assertIsCoerce' "MyText'" (fromJSONKey :: FromJSONKeyFunction MyText')
328327#endif