Skip to content

Commit 496f7ab

Browse files
phadejBoarders
andcommitted
Change Object to use an opaque TextMap interface
Co-authored-by: Callan McGill <[email protected]>
1 parent 92bfca0 commit 496f7ab

File tree

15 files changed

+329
-83
lines changed

15 files changed

+329
-83
lines changed

aeson.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ library
6868
Data.Aeson.Internal
6969
Data.Aeson.Internal.Time
7070
Data.Aeson.Parser.Internal
71+
Data.Aeson.TextMap
7172

7273
-- Deprecated modules
7374
exposed-modules:

benchmarks/aeson-benchmarks.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ library
8383
Data.Aeson.Parser.UnescapeFFI
8484
Data.Aeson.Parser.UnescapePure
8585
Data.Aeson.Text
86+
Data.Aeson.TextMap
8687
Data.Aeson.TH
8788
Data.Aeson.Types
8889
Data.Aeson.Types.Class

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ For the latest version of this document, please see [https://github.com/haskell/
88

99
To get `fast` compilation effect cabal-install users may specify `optimization: False`.
1010

11+
* Make map type used by Object abstract so the underlying implementation can
12+
be modified.
13+
1114
### 1.5.6.0
1215
* Make `Show Value` instance print object keys in lexicographic order.
1316

src/Data/Aeson/Encoding/Builder.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Prelude.Compat
4242

4343
import Data.Aeson.Internal.Time
4444
import Data.Aeson.Types.Internal (Value (..))
45+
import qualified Data.Aeson.TextMap as TM
4546
import Data.ByteString.Builder as B
4647
import Data.ByteString.Builder.Prim as BP
4748
import Data.ByteString.Builder.Scientific (scientificBuilder)
@@ -54,7 +55,6 @@ import Data.Time.Calendar.Month.Compat (Month, toYearMonth)
5455
import Data.Time.Calendar.Quarter.Compat (Quarter, toYearQuarter, QuarterOfYear (..))
5556
import Data.Time.LocalTime
5657
import Data.Word (Word8)
57-
import qualified Data.HashMap.Strict as HMS
5858
import qualified Data.Text as T
5959
import qualified Data.Vector as V
6060

@@ -90,8 +90,8 @@ array v
9090
withComma a z = B.char8 ',' <> encodeToBuilder a <> z
9191

9292
-- Encode a JSON object.
93-
object :: HMS.HashMap T.Text Value -> Builder
94-
object m = case HMS.toList m of
93+
object :: TM.TextMap Value -> Builder
94+
object m = case TM.toList m of
9595
(x:xs) -> B.char8 '{' <> one x <> foldr withComma (B.char8 '}') xs
9696
_ -> emptyObject_
9797
where

src/Data/Aeson/Internal/Functions.hs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@
99
-- Portability: portable
1010

1111
module Data.Aeson.Internal.Functions
12-
(
13-
mapHashKeyVal
12+
( mapTextKeyVal
1413
, mapKeyVal
1514
, mapKey
1615
) where
1716

1817
import Prelude.Compat
1918

2019
import Data.Hashable (Hashable)
20+
import qualified Data.Aeson.TextMap as TM
2121
import qualified Data.HashMap.Strict as H
2222
import qualified Data.Map as M
23+
import qualified Data.Text as T
2324

24-
-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.
25-
mapHashKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)
26-
-> M.Map k1 v1 -> H.HashMap k2 v2
27-
mapHashKeyVal fk kv = M.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty
28-
{-# INLINE mapHashKeyVal #-}
25+
-- | Transform a 'M.Map' into a 'TM.TextMap' while transforming the keys.
26+
mapTextKeyVal :: (k -> T.Text) -> (v1 -> v2)
27+
-> M.Map k v1 -> TM.TextMap v2
28+
mapTextKeyVal fk kv = M.foldrWithKey (\k v -> TM.insert (fk k) (kv v)) TM.empty
29+
{-# INLINE mapTextKeyVal #-}
2930

3031
-- | Transform the keys and values of a 'H.HashMap'.
3132
mapKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)
@@ -37,3 +38,4 @@ mapKeyVal fk kv = H.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty
3738
mapKey :: (Eq k2, Hashable k2) => (k1 -> k2) -> H.HashMap k1 v -> H.HashMap k2 v
3839
mapKey fk = mapKeyVal fk id
3940
{-# INLINE mapKey #-}
41+

src/Data/Aeson/Parser/Internal.hs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import Prelude.Compat
5252
import Control.Applicative ((<|>))
5353
import Control.Monad (void, when)
5454
import Data.Aeson.Types.Internal (IResult(..), JSONPath, Object, Result(..), Value(..))
55+
import qualified Data.Aeson.TextMap as TM
5556
import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
5657
import Data.Function (fix)
5758
import Data.Functor.Compat (($>))
@@ -68,7 +69,6 @@ import qualified Data.ByteString.Lazy as L
6869
import qualified Data.ByteString.Lazy as BSL
6970
import qualified Data.ByteString.Lazy.Char8 as C
7071
import qualified Data.ByteString.Builder as B
71-
import qualified Data.HashMap.Strict as H
7272
import qualified Data.Scientific as Sci
7373
import Data.Aeson.Parser.Unescape (unescapeText)
7474

@@ -146,16 +146,18 @@ object_' mkObject val' = {-# SCC "object_'" #-} do
146146
{-# INLINE object_' #-}
147147

148148
objectValues :: ([(Text, Value)] -> Either String Object)
149-
-> Parser Text -> Parser Value -> Parser (H.HashMap Text Value)
149+
-> Parser Text -> Parser Value -> Parser (TM.TextMap Value)
150150
objectValues mkObject str val = do
151151
skipSpace
152152
w <- A.peekWord8'
153153
if w == CLOSE_CURLY
154-
then A.anyWord8 >> return H.empty
154+
then A.anyWord8 >> return TM.empty
155155
else loop []
156156
where
157-
-- Why use acc pattern here, you may ask? because 'H.fromList' use 'unsafeInsert'
158-
-- and it's much faster because it's doing in place update to the 'HashMap'!
157+
-- Why use acc pattern here, you may ask? because then the underlying 'TM.fromList'
158+
-- implementation can make use of mutation when constructing a map. For example,
159+
-- 'HashMap` uses 'unsafeInsert' and it's much faster because it's doing in place
160+
-- update to the 'HashMap'!
159161
loop acc = do
160162
k <- (str A.<?> "object key") <* skipSpace <* (char ':' A.<?> "':'")
161163
v <- (val A.<?> "object value") <* skipSpace
@@ -196,7 +198,7 @@ arrayValues val = do
196198

197199
-- | Parse any JSON value. Synonym of 'json'.
198200
value :: Parser Value
199-
value = jsonWith (pure . H.fromList)
201+
value = jsonWith (pure . TM.fromList)
200202

201203
-- | Parse any JSON value.
202204
--
@@ -206,7 +208,7 @@ value = jsonWith (pure . H.fromList)
206208
--
207209
-- ==== __Examples__
208210
--
209-
-- 'json' keeps only the first occurence of each key, using 'HashMap.Lazy.fromList'.
211+
-- 'json' keeps only the first occurence of each key, using 'Data.Aeson.TextMap.fromList'.
210212
--
211213
-- @
212214
-- 'json' = 'jsonWith' ('Right' '.' 'H.fromList')
@@ -249,7 +251,7 @@ jsonWith mkObject = fix $ \value_ -> do
249251

250252
-- | Variant of 'json' which keeps only the last occurence of every key.
251253
jsonLast :: Parser Value
252-
jsonLast = jsonWith (Right . H.fromListWith (const id))
254+
jsonLast = jsonWith (Right . TM.fromListWith (const id))
253255

254256
-- | Variant of 'json' wrapping all object mappings in 'Array' to preserve
255257
-- key-value pairs with the same keys.
@@ -267,19 +269,20 @@ jsonNoDup = jsonWith parseListNoDup
267269
-- fromList [("apple",Array [Bool False,Bool True]),("orange",Array [Bool False])]
268270
fromListAccum :: [(Text, Value)] -> Object
269271
fromListAccum =
270-
fmap (Array . Vector.fromList . ($ [])) . H.fromListWith (.) . (fmap . fmap) (:)
272+
fmap (Array . Vector.fromList . ($ [])) . TM.fromListWith (.) . (fmap . fmap) (:)
271273

272274
-- | @'fromListNoDup' kvs@ fails if @kvs@ contains duplicate keys.
273275
parseListNoDup :: [(Text, Value)] -> Either String Object
274276
parseListNoDup =
275-
H.traverseWithKey unwrap . H.fromListWith (\_ _ -> Nothing) . (fmap . fmap) Just
277+
TM.traverseWithKey unwrap . TM.fromListWith (\_ _ -> Nothing) . (fmap . fmap) Just
276278
where
279+
277280
unwrap k Nothing = Left $ "found duplicate key: " ++ show k
278281
unwrap _ (Just v) = Right v
279282

280283
-- | Strict version of 'value'. Synonym of 'json''.
281284
value' :: Parser Value
282-
value' = jsonWith' (pure . H.fromList)
285+
value' = jsonWith' (pure . TM.fromList)
283286

284287
-- | Strict version of 'jsonWith'.
285288
jsonWith' :: ([(Text, Value)] -> Either String Object) -> Parser Value
@@ -304,7 +307,7 @@ jsonWith' mkObject = fix $ \value_ -> do
304307

305308
-- | Variant of 'json'' which keeps only the last occurence of every key.
306309
jsonLast' :: Parser Value
307-
jsonLast' = jsonWith' (pure . H.fromListWith (const id))
310+
jsonLast' = jsonWith' (pure . TM.fromListWith (const id))
308311

309312
-- | Variant of 'json'' wrapping all object mappings in 'Array' to preserve
310313
-- key-value pairs with the same keys.

src/Data/Aeson/TH.hs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ import Data.Aeson.Types (Options(..), Parser, SumEncoding(..), Value(..), defaul
128128
import Data.Aeson.Types.Internal ((<?>), JSONPathElement(Key))
129129
import Data.Aeson.Types.FromJSON (parseOptionalFieldWith)
130130
import Data.Aeson.Types.ToJSON (fromPairs, pair)
131+
import qualified Data.Aeson.TextMap as TM
131132
import Control.Monad (liftM2, unless, when)
132133
import Data.Foldable (foldr')
133134
#if MIN_VERSION_template_haskell(2,8,0) && !MIN_VERSION_template_haskell(2,10,0)
@@ -147,7 +148,7 @@ import Language.Haskell.TH.Syntax (mkNameG_tc)
147148
import Text.Printf (printf)
148149
import qualified Data.Aeson.Encoding.Internal as E
149150
import qualified Data.Foldable as F (all)
150-
import qualified Data.HashMap.Strict as H (difference, fromList, keys, lookup, toList)
151+
--import qualified Data.HashMap.Strict as H (difference, fromList, keys, toList)
151152
import qualified Data.List.NonEmpty as NE (length, reverse)
152153
import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
153154
#if !MIN_VERSION_base(4,16,0)
@@ -849,7 +850,7 @@ consFromJSON jc tName opts instTys cons = do
849850
parseObjectWithSingleField tvMap obj = do
850851
conKey <- newName "conKey"
851852
conVal <- newName "conVal"
852-
caseE ([e|H.toList|] `appE` varE obj)
853+
caseE ([e|TM.toList|] `appE` varE obj)
853854
[ match (listP [tupP [varP conKey, varP conVal]])
854855
(normalB $ parseContents tvMap conKey (Right conVal) 'conNotFoundFailObjectSingleField)
855856
[]
@@ -947,11 +948,11 @@ parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject =
947948
where
948949
tagFieldNameAppender =
949950
if inTaggedObject then (tagFieldName (sumEncoding opts) :) else id
950-
knownFields = appE [|H.fromList|] $ listE $
951+
knownFields = appE [|TM.fromList|] $ listE $
951952
map (\knownName -> tupE [appE [|T.pack|] $ litE $ stringL knownName, [|()|]]) $
952953
tagFieldNameAppender $ map (fieldLabel opts) fields
953954
checkUnknownRecords =
954-
caseE (appE [|H.keys|] $ infixApp (varE obj) [|H.difference|] knownFields)
955+
caseE (appE [|TM.keys|] $ infixApp (varE obj) [|TM.difference|] knownFields)
955956
[ match (listP []) (normalB [|return ()|]) []
956957
, newName "unknownFields" >>=
957958
\unknownFields -> match (varP unknownFields)
@@ -1149,7 +1150,7 @@ instance INCOHERENT_ LookupField (Semigroup.Option a) where
11491150
lookupFieldWith :: (Value -> Parser a) -> String -> String
11501151
-> Object -> T.Text -> Parser a
11511152
lookupFieldWith pj tName rec obj key =
1152-
case H.lookup key obj of
1153+
case TM.lookup key obj of
11531154
Nothing -> unknownFieldFail tName rec (T.unpack key)
11541155
Just v -> pj v <?> Key key
11551156

src/Data/Aeson/Text.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ import Prelude.Compat
2626

2727
import Data.Aeson.Types (Value(..), ToJSON(..))
2828
import Data.Aeson.Encoding (encodingToLazyByteString)
29+
import qualified Data.Aeson.TextMap as TM
2930
import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
3031
import Data.Text.Lazy.Builder
3132
import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
3233
import Numeric (showHex)
33-
import qualified Data.HashMap.Strict as H
3434
import qualified Data.Text as T
3535
import qualified Data.Text.Lazy as LT
3636
import qualified Data.Text.Lazy.Encoding as LT
@@ -66,7 +66,7 @@ encodeToTextBuilder =
6666
V.foldr f (singleton ']') (V.unsafeTail v)
6767
where f a z = singleton ',' <> go a <> z
6868
go (Object m) = {-# SCC "go/Object" #-}
69-
case H.toList m of
69+
case TM.toList m of
7070
(x:xs) -> singleton '{' <> one x <> foldr f (singleton '}') xs
7171
_ -> "{}"
7272
where f a z = singleton ',' <> one a <> z

0 commit comments

Comments
 (0)