@@ -1,4 +1,5 @@
1−{-# LANGUAGE CPP, DefaultSignatures, FlexibleContexts, DeriveFunctor #-}
1+{-# LANGUAGE CPP, GADTs, DefaultSignatures, FlexibleContexts, DeriveFunctor,
2+ ScopedTypeVariables #-}
23
34-- |
45-- Module: Data.Aeson.Types.Class
@@ -26,6 +27,8 @@ module Data.Aeson.Types.Class
2627 -- * Classes and types for map keys
2728 , ToJSONKeyFunction(..)
2829 , FromJSONKeyFunction(..)
30+ , fromJSONKeyCoerce
31+ , coerceFromJSONKeyFunction
2932 -- * Object key-value pairs
3033 , KeyValue(..)
3134 -- * Functions needed for documentation
@@ -41,6 +44,18 @@ import qualified Data.ByteString.Builder as B
4144import qualified Data.Aeson.Encode.Builder as E
4245import qualified Data.Vector as V
4346
47+#define HAS_COERCIBLE MIN_VERSION_base(4,7,0)
48+
49+#if HAS_COERCIBLE
50+import Data.Coerce (Coercible, coerce)
51+coerce' :: Coercible a b => a -> b
52+coerce' = coerce
53+#else
54+import Unsafe.Coerce (unsafeCoerce)
55+coerce' :: a -> b
56+coerce' = unsafeCoerce
57+#endif
58+
4459-- | Class of generic representation types ('Rep') that can be converted to
4560-- JSON.
4661class GToJSON f where
@@ -261,13 +276,50 @@ data ToJSONKeyFunction a
261276 = ToJSONKeyText (a -> Text, a -> Encoding)
262277 | ToJSONKeyValue (a -> Value, a -> Encoding)
263278
279+-- | With GHC 7.8 + we carry around 'Coercible Text a' dictionary,
280+-- to have even some amount of safety net.
281+-- Unfortunately we cannot enforce that 'Hashable' instance agree on the type level
282+--
283+-- ATM this type is intentionally not exported. FromJSONKeyFunction can be inspected,
284+-- but cannot be constructed.
285+data CoerceText a where
286+#if HAS_COERCIBLE
287+ CoerceText :: Coercible Text a => CoerceText a
288+#else
289+ CoerceText :: CoerceText a
290+#endif
291+
264292data FromJSONKeyFunction a
265− = FromJSONKeyCoerce
293+ = FromJSONKeyCoerce (CoerceText a)
266294 | FromJSONKeyText (Text -> a)
267295 | FromJSONKeyTextParser (Text -> Parser a)
268296 | FromJSONKeyValue (Value -> Parser a)
269− deriving (Functor)
270297
298+instance Functor FromJSONKeyFunction where
299+ fmap h (FromJSONKeyCoerce CoerceText) = FromJSONKeyText (h . coerce')
300+ fmap h (FromJSONKeyText f) = FromJSONKeyText (h . f)
301+ fmap h (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap h . f)
302+ fmap h (FromJSONKeyValue f) = FromJSONKeyValue (fmap h . f)
303+
304+-- | Construct 'FromJSONKeyFunction' for types coercible from 'Text'. This
305+-- conversion is still unsafe, as 'Hashable' and 'Eq' instances of @a@ should be
306+-- compatible with 'Text' i.e. hash values be equal for wrapped values as well.
307+fromJSONKeyCoerce ::
308+#if HAS_COERCIBLE
309+ Coercible Text a =>
310+#endif
311+ FromJSONKeyFunction a
312+fromJSONKeyCoerce = FromJSONKeyCoerce CoerceText
313+
314+coerceFromJSONKeyFunction ::
315+#if HAS_COERCIBLE
316+ Coercible a b =>
317+#endif
318+ FromJSONKeyFunction a -> FromJSONKeyFunction b
319+coerceFromJSONKeyFunction (FromJSONKeyCoerce CoerceText) = FromJSONKeyCoerce CoerceText
320+coerceFromJSONKeyFunction (FromJSONKeyText f) = FromJSONKeyText (coerce' . f)
321+coerceFromJSONKeyFunction (FromJSONKeyTextParser f) = FromJSONKeyTextParser (fmap coerce' . f)
322+coerceFromJSONKeyFunction (FromJSONKeyValue f) = FromJSONKeyValue (fmap coerce' . f)
271323
272324-- | Fail parsing due to a type mismatch, with a descriptive message.
273325--