@@ -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(..)
@@ -55,9 +56,9 @@ module Data.Aeson.Types.ToJSON (
5556import Prelude ()
5657import Prelude.Compat
5758
58−import Data.Aeson.Encoding.Internal (Encoding (..), Series, dict, emptyArray_,
59− tuple, (>*<))
60−import Data.Aeson.Functions (mapHashKeyVal, mapKeyVal)
59+import Data.Aeson.Encoding.Internal (Encoding, Encoding', Series, dict,
60+ emptyArray_, tuple, (>*<), (><))
61+import Data.Aeson.Internal.Functions (mapHashKeyVal, mapKeyVal)
6162import Data.Aeson.Types.Generic
6263import Data.Aeson.Types.Internal
6364
@@ -92,35 +93,30 @@ import Foreign.Storable (Storable)
9293import GHC.Generics
9394import Numeric.Natural (Natural)
9495
95−import qualified Data.ByteString.Builder as B (toLazyByteString)
96−import qualified Data.ByteString.Lazy as L
97−import qualified Data.DList as DList
98−import qualified Data.HashMap.Strict as H
99−import qualified Data.HashSet as HashSet
100−import qualified Data.IntMap as IntMap
101−import qualified Data.IntSet as IntSet
102−import qualified Data.List.NonEmpty as NE
103−import qualified Data.Map as M
104−import qualified Data.Scientific as Scientific
105−import qualified Data.Sequence as Seq
106−import qualified Data.Set as Set
107−import qualified Data.Text as T
108−import qualified Data.Text.Encoding as T
109−import qualified Data.Text.Lazy as LT
110−import qualified Data.Tree as Tree
111−import qualified Data.Vector as V
112−import qualified Data.Vector.Generic as VG
113−import qualified Data.Vector.Mutable as VM
114−import qualified Data.Vector.Primitive as VP
115−import qualified Data.Vector.Storable as VS
116−import qualified Data.Vector.Unboxed as VU
96+import qualified Data.ByteString.Lazy as L
97+import qualified Data.DList as DList
98+import qualified Data.HashMap.Strict as H
99+import qualified Data.HashSet as HashSet
100+import qualified Data.IntMap as IntMap
101+import qualified Data.IntSet as IntSet
102+import qualified Data.List.NonEmpty as NE
103+import qualified Data.Map as M
104+import qualified Data.Scientific as Scientific
105+import qualified Data.Sequence as Seq
106+import qualified Data.Set as Set
107+import qualified Data.Text as T
108+import qualified Data.Text.Encoding as T
109+import qualified Data.Text.Lazy as LT
110+import qualified Data.Tree as Tree
111+import qualified Data.Vector as V
112+import qualified Data.Vector.Generic as VG
113+import qualified Data.Vector.Mutable as VM
114+import qualified Data.Vector.Primitive as VP
115+import qualified Data.Vector.Storable as VS
116+import qualified Data.Vector.Unboxed as VU
117117
118118toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
119−toJSONPair keySerialiser valSerializer (a, b) = Array $ V.create $ do
120− mv <- VM.unsafeNew 2
121− VM.unsafeWrite mv 0 (keySerialiser a)
122− VM.unsafeWrite mv 1 (valSerializer b)
123− return mv
119+toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
124120{-# INLINE toJSONPair #-}
125121
126122realFloatToJSON :: RealFloat a => a -> Value
@@ -418,22 +414,44 @@ class ToJSONKey a where
418414 -- container.
419415 toJSONKey :: ToJSONKeyFunction a
420416 default toJSONKey :: ToJSON a => ToJSONKeyFunction a
421− toJSONKey = ToJSONKeyValue (toJSON, toEncoding)
417+ toJSONKey = ToJSONKeyValue toJSON toEncoding
418+
422419 -- | This is similar in spirit to the 'showsList' method of 'Show'.
423420 -- It makes it possible to give 'String' keys special treatment
424421 -- without using @OverlappingInstances@.
425422 toJSONKeyList :: ToJSONKeyFunction [a]
426423 default toJSONKeyList :: ToJSON a => ToJSONKeyFunction [a]
427− toJSONKeyList = ToJSONKeyValue (toJSON, toEncoding)
424+ toJSONKeyList = ToJSONKeyValue toJSON toEncoding
428425
429426data ToJSONKeyFunction a
430− = ToJSONKeyText (a -> Text, a -> Encoding) -- ^ key is encoded to string, produces object
431− | 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)
442+
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
432449
450+-- | Contravariant map, as 'ToJSONKeyFunction' is a contravariant functor.
433451contramapToJSONKeyFunction :: (b -> a) -> ToJSONKeyFunction a -> ToJSONKeyFunction b
434452contramapToJSONKeyFunction h x = case x of
435− ToJSONKeyText (f,g) -> ToJSONKeyText (f . h, g . h)
436− 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)
437455
438456-------------------------------------------------------------------------------
439457-- Lifings of FromJSON and ToJSON to unary and binary type constructors
@@ -676,7 +694,7 @@ instance ( AllNullary (a :+: b) allNullary
676694 -- 'allNullaryToStringTag' option is set they are encoded to
677695 -- strings. This distinction is made by 'sumToEncoding':
678696 gToEncoding opts pa te tel
679− = (unTagged :: Tagged allNullary Encoding -> Encoding)
697+ = (unTagged :: Tagged allNullary (Encoding) -> Encoding)
680698 . sumToEncoding opts pa te tel
681699
682700instance (ToJSON1 f, GToEncoding One g) => GToEncoding One (f :.: g) where
@@ -728,7 +746,7 @@ nonAllNullarySumToJSON opts pa tj tjl =
728746class SumToEncoding arity f allNullary where
729747 sumToEncoding :: Options -> Proxy arity
730748 -> (a -> Encoding) -> ([a] -> Encoding)
731− -> f a -> Tagged allNullary Encoding
749+ -> f a -> Tagged allNullary (Encoding)
732750
733751instance ( GetConName f
734752 , TaggedObjectEnc arity f
@@ -822,29 +840,28 @@ instance ( IsRecord a isRecord
822840 , Constructor c
823841 ) => TaggedObjectEnc arity (C1 c a) where
824842 taggedObjectEnc opts pa tagFieldName contentsFieldName te tel v =
825− E.openCurly <>
826− (toEncoding tagFieldName <>
827− E.colon <>
828− toEncoding (constructorTagModifier opts (conName (undefined :: t c a p)))) <>
829− ((unTagged :: Tagged isRecord Encoding -> Encoding) .
830− taggedObjectEnc' opts pa contentsFieldName te tel . unM1 $ v) <>
831− E.closeCurly
843+ E.wrapObject $
844+ (E.string tagFieldName ><
845+ E.colon ><
846+ toEncoding (constructorTagModifier opts (conName (undefined :: t c a p)))) ><
847+ ((unTagged :: Tagged isRecord (Encoding) -> Encoding) .
848+ taggedObjectEnc' opts pa contentsFieldName te tel . unM1 $ v)
832849
833850class TaggedObjectEnc' arity f isRecord where
834851 taggedObjectEnc' :: Options -> Proxy arity
835852 -> String -> (a -> Encoding) -> ([a] -> Encoding)
836− -> f a -> Tagged isRecord Encoding
853+ -> f a -> Tagged isRecord (Encoding)
837854
838855instance OVERLAPPING_ TaggedObjectEnc' arity U1 False where
839− taggedObjectEnc' _ _ _ _ _ _ = Tagged mempty
856+ taggedObjectEnc' _ _ _ _ _ _ = Tagged E.empty
840857
841858instance (RecordToEncoding arity f) => TaggedObjectEnc' arity f True where
842− taggedObjectEnc' opts pa _ te tel = Tagged . (E.comma <>) . fst
859+ taggedObjectEnc' opts pa _ te tel = Tagged . (E.comma ><) . fst
843860 . recordToEncoding opts pa te tel
844861
845862instance (GToEncoding arity f) => TaggedObjectEnc' arity f False where
846863 taggedObjectEnc' opts pa contentsFieldName te tel =
847− Tagged . (\z -> E.comma <> toEncoding contentsFieldName <> E.colon <> z) .
864+ Tagged . (\z -> E.comma >< toEncoding contentsFieldName >< E.colon >< z) .
848865 gToEncoding opts pa te tel
849866
850867--------------------------------------------------------------------------------
@@ -946,13 +963,13 @@ class ConsToEncoding' arity f isRecord where
946963 consToEncoding' :: Options -> Proxy arity
947964 -> Bool -- ^ Are we a record with one field?
948965 -> (a -> Encoding) -> ([a] -> Encoding)
949− -> f a -> Tagged isRecord Encoding
966+ -> f a -> Tagged isRecord (Encoding)
950967
951968instance ( IsRecord f isRecord
952969 , ConsToEncoding' arity f isRecord
953970 ) => ConsToEncoding arity f where
954971 consToEncoding opts pa te tel =
955− (unTagged :: Tagged isRecord Encoding -> Encoding)
972+ (unTagged :: Tagged isRecord (Encoding) -> (Encoding))
956973 . consToEncoding' opts pa (isUnary (undefined :: f a)) te tel
957974
958975instance (RecordToEncoding arity f) => ConsToEncoding' arity f True where
@@ -1003,39 +1020,40 @@ class RecordToEncoding arity f where
10031020 -- of the field (without the key); 'Nothing' otherwise
10041021 recordToEncoding :: Options -> Proxy arity
10051022 -> (a -> Encoding) -> ([a] -> Encoding)
1006− -> f a -> (Encoding, Maybe Encoding)
1023+ -> f a -> (Encoding, Maybe (Encoding))
10071024
10081025instance ( RecordToEncoding arity a
10091026 , RecordToEncoding arity b
10101027 ) => RecordToEncoding arity (a :*: b) where
10111028 recordToEncoding opts pa te tel (a :*: b) | omitNothingFields opts =
1012− (mconcat $ intersperse E.comma $
1029+ (E.econcat $ intersperse E.comma $
10131030 filter (not . E.nullEncoding)
10141031 [ fst (recordToEncoding opts pa te tel a)
10151032 , fst (recordToEncoding opts pa te tel b) ]
10161033 , Nothing)
10171034 recordToEncoding opts pa te tel (a :*: b) =
1018− (fst (recordToEncoding opts pa te tel a) <> E.comma <>
1035+ (fst (recordToEncoding opts pa te tel a) >< E.comma ><
10191036 fst (recordToEncoding opts pa te tel b),
10201037 Nothing)
10211038
1039+
10221040instance (Selector s, GToEncoding arity a) => RecordToEncoding arity (S1 s a) where
10231041 recordToEncoding = fieldToEncoding
10241042
10251043instance OVERLAPPING_ (Selector s, ToJSON a) =>
10261044 RecordToEncoding arity (S1 s (K1 i (Maybe a))) where
10271045 recordToEncoding opts _ _ _ (M1 k1) | omitNothingFields opts
1028− , K1 Nothing <- k1 = (mempty, Nothing)
1046+ , K1 Nothing <- k1 = (E.empty, Nothing)
10291047 recordToEncoding opts pa te tel m1 = fieldToEncoding opts pa te tel m1
10301048
10311049fieldToEncoding :: (Selector s, GToEncoding arity a)
10321050 => Options -> Proxy arity
10331051 -> (p -> Encoding) -> ([p] -> Encoding)
1034− -> S1 s a p -> (Encoding, Maybe Encoding)
1052+ -> S1 s a p -> (Encoding, Maybe (Encoding))
10351053fieldToEncoding opts pa te tel m1 =
10361054 let keyBuilder = toEncoding (fieldLabelModifier opts $ selName m1)
10371055 valueBuilder = gToEncoding opts pa te tel (unM1 m1)
1038− in (keyBuilder <> E.colon <> valueBuilder, Just valueBuilder)
1056+ in (keyBuilder >< E.colon >< valueBuilder, Just valueBuilder)
10391057
10401058--------------------------------------------------------------------------------
10411059
@@ -1070,21 +1088,20 @@ instance OVERLAPPABLE_ (GToJSON arity a) => WriteProduct arity a where
10701088class EncodeProduct arity f where
10711089 encodeProduct :: Options -> Proxy arity
10721090 -> (a -> Encoding) -> ([a] -> Encoding)
1073− -> f a -> Encoding
1091+ -> f a -> Encoding' E.InArray
10741092
10751093instance ( EncodeProduct arity a
10761094 , EncodeProduct arity b
10771095 ) => EncodeProduct arity (a :*: b) where
10781096 encodeProduct opts pa te tel (a :*: b) | omitNothingFields opts =
1079− mconcat $ intersperse E.comma $
1097+ E.econcat $ intersperse E.comma $
10801098 filter (not . E.nullEncoding)
10811099 [encodeProduct opts pa te tel a, encodeProduct opts pa te tel b]
1082− encodeProduct opts pa te tel (a :*: b) = encodeProduct opts pa te tel a <>
1083− E.comma <>
1100+ encodeProduct opts pa te tel (a :*: b) = encodeProduct opts pa te tel a >*<
10841101 encodeProduct opts pa te tel b
10851102
10861103instance OVERLAPPABLE_ (GToEncoding arity a) => EncodeProduct arity a where
1087− encodeProduct opts = gToEncoding opts
1104+ encodeProduct opts pa te tel a = E.retagEncoding $ gToEncoding opts pa te tel a
10881105
10891106--------------------------------------------------------------------------------
10901107
@@ -1130,12 +1147,12 @@ instance ( GToEncoding arity a
11301147 , Constructor c
11311148 ) => ObjectWithSingleFieldEnc arity (C1 c a) where
11321149 objectWithSingleFieldEnc opts pa te tel v =
1133− E.openCurly <>
1150+ E.openCurly ><
11341151 toEncoding
11351152 (constructorTagModifier opts
1136− (conName (undefined :: t c a p))) <>
1137− E.colon <>
1138− gToEncoding opts pa te tel v <>
1153+ (conName (undefined :: t c a p))) ><
1154+ E.colon ><
1155+ gToEncoding opts pa te tel v ><
11391156 E.closeCurly
11401157
11411158-------------------------------------------------------------------------------
@@ -1237,10 +1254,7 @@ instance ToJSON Bool where
12371254 {-# INLINE toEncoding #-}
12381255
12391256instance ToJSONKey Bool where
1240− toJSONKey = ToJSONKeyText
1241− ( (\x -> if x then "true" else "false")
1242− , (\x -> toEncoding $ if x then ("true" :: Text) else "false")
1243− )
1257+ toJSONKey = toJSONKeyText $ \x -> if x then "true" else "false"
12441258
12451259
12461260instance ToJSON Ordering where
@@ -1485,9 +1499,10 @@ instance ToJSON Text where
14851499 {-# INLINE toEncoding #-}
14861500
14871501instance ToJSONKey Text where
1488− toJSONKey = ToJSONKeyText (id, toEncoding)
1502+ toJSONKey = toJSONKeyText id
14891503 {-# INLINE toJSONKey #-}
14901504
1505+
14911506instance ToJSON LT.Text where
14921507 toJSON = String . LT.toStrict
14931508 {-# INLINE toJSON #-}
@@ -1495,6 +1510,10 @@ instance ToJSON LT.Text where
14951510 toEncoding = E.lazyText
14961511 {-# INLINE toEncoding #-}
14971512
1513+instance ToJSONKey LT.Text where
1514+ toJSONKey = toJSONKeyText LT.toStrict
1515+
1516+
14981517instance ToJSON Version where
14991518 toJSON = toJSON . showVersion
15001519 {-# INLINE toJSON #-}
@@ -1502,6 +1521,9 @@ instance ToJSON Version where
15021521 toEncoding = toEncoding . showVersion
15031522 {-# INLINE toEncoding #-}
15041523
1524+instance ToJSONKey Version where
1525+ toJSONKey = toJSONKeyText (T.pack . showVersion)
1526+
15051527-------------------------------------------------------------------------------
15061528-- semigroups NonEmpty
15071529-------------------------------------------------------------------------------
@@ -1617,14 +1639,14 @@ instance ToJSON a => ToJSON (IntMap.IntMap a) where
16171639
16181640instance ToJSONKey k => ToJSON1 (M.Map k) where
16191641 liftToJSON g _ = case toJSONKey of
1620− ToJSONKeyText (f,_) -> Object . mapHashKeyVal f g
1621− ToJSONKeyValue (f,_) -> Array . V.fromList . map (toJSONPair f g) . M.toList
1642+ ToJSONKeyText f _ -> Object . mapHashKeyVal f g
1643+ ToJSONKeyValue f _ -> Array . V.fromList . map (toJSONPair f g) . M.toList
16221644 {-# INLINE liftToJSON #-}
16231645
16241646 -- liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> M.Map k a -> Encoding
16251647 liftToEncoding g _ = case toJSONKey of
1626− ToJSONKeyText (_,f) -> dict f g M.foldrWithKey
1627− ToJSONKeyValue (_,f) -> listEncoding (pairEncoding f) . M.toList
1648+ ToJSONKeyText _ f -> dict f g M.foldrWithKey
1649+ ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . M.toList
16281650 where
16291651 -- pairEncoding :: (k -> Encoding) -> (k, a) -> Encoding
16301652 pairEncoding f (a, b) = tuple $ f a >*< g b
@@ -1736,14 +1758,14 @@ instance (ToJSON a) => ToJSON (HashSet.HashSet a) where
17361758
17371759instance ToJSONKey k => ToJSON1 (H.HashMap k) where
17381760 liftToJSON g _ = case toJSONKey of
1739− ToJSONKeyText (f,_) -> Object . mapKeyVal f g
1740− ToJSONKeyValue (f,_) -> Array . V.fromList . map (toJSONPair f g) . H.toList
1761+ ToJSONKeyText f _ -> Object . mapKeyVal f g
1762+ ToJSONKeyValue f _ -> Array . V.fromList . map (toJSONPair f g) . H.toList
17411763 {-# INLINE liftToJSON #-}
17421764
17431765 -- liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> H.HashMap k a -> Encoding
17441766 liftToEncoding g _ = case toJSONKey of
1745− ToJSONKeyText (_,f) -> dict f g H.foldrWithKey
1746− ToJSONKeyValue (_,f) -> listEncoding (pairEncoding f) . H.toList
1767+ ToJSONKeyText _ f -> dict f g H.foldrWithKey
1768+ ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . H.toList
17471769 where
17481770 -- pairEncoding :: (k -> Encoding) -> (k, a) -> Encoding
17491771 pairEncoding f (a, b) = tuple $ f a >*< g b
@@ -1823,13 +1845,12 @@ instance ToJSONKey UTCTime where
18231845 toJSONKey = toJSONKeyTextEnc E.utcTime
18241846
18251847-- | Encode something t a JSON string.
1826−stringEncoding :: Encoding -> Value
1848+stringEncoding :: Encoding' Text -> Value
18271849stringEncoding = String
18281850 . T.dropAround (== '"')
18291851 . T.decodeLatin1
18301852 . L.toStrict
1831− . B.toLazyByteString
1832− . fromEncoding
1853+ . E.encodingToLazyByteString
18331854{-# INLINE stringEncoding #-}
18341855
18351856instance ToJSON NominalDiffTime where
@@ -1922,22 +1943,13 @@ instance ToJSONKey b => ToJSONKey (Tagged a b) where
19221943-- Instances for converting t map keys
19231944-------------------------------------------------------------------------------
19241945
1925−-- | TODO: Move ToJSONKEyFunction to .Types(.Internal),
1926−-- export and document this function from there
1927−toJSONKeyTextEnc :: (a -> Encoding) -> ToJSONKeyFunction a
1928−toJSONKeyTextEnc e = ToJSONKeyText
1929− -- TODO: dropAround is also used in stringEncoding, which is unfortunate atm
1930− ( T.dropAround (== '"') . T.decodeLatin1 . L.toStrict . B.toLazyByteString . fromEncoding . e
1931− , e
1932− )
1933−
19341946instance (ToJSON a, ToJSON b) => ToJSONKey (a,b)
19351947instance (ToJSON a, ToJSON b, ToJSON c) => ToJSONKey (a,b,c)
19361948instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSONKey (a,b,c,d)
19371949
19381950instance ToJSONKey Char where
1939− toJSONKey = ToJSONKeyText (T.singleton, toEncoding)
1940− toJSONKeyList = ToJSONKeyText (T.pack, toEncoding . T.pack)
1951+ toJSONKey = ToJSONKeyText T.singleton (E.string . (:[]))
1952+ toJSONKeyList = toJSONKeyText T.pack
19411953
19421954instance (ToJSONKey a, ToJSON a) => ToJSONKey [a] where
19431955 toJSONKey = toJSONKeyList