Skip to main content

Aeson 2 Object Coercion (Part 2)

I did not plan on spending any more time on optimizing aeson and ginger after writing the Aeson 2 Object Coercion blog entry, but I ended up doing a few more experiments.

First, I realized that my rawJSONToGVal change also traverses objects twice, transforming keys and values separately. Curious to see the performance impact, I moved the helper function of the Map Text instance to the top level so that it can be used by rawJSONToGVal as well. Note that I added an INLINE pragma to ensure that it is (still?) inlined even though it is used in two locations.

instance ToGVal m v => ToGVal m (Map Text v) where
    toGVal xs = mapToGVal (Map.map toGVal xs)

mapToGVal :: Map Text (GVal m) -> GVal m
mapToGVal xs =
    def
        { asHtml = mconcat . Prelude.map asHtml . Map.elems $ xs
        , asText = mconcat . Prelude.map asText . Map.elems $ xs
        , asBytes = mconcat . Prelude.map asBytes . Map.elems $ xs
        , asBoolean = not . Map.null $ xs
        , isNull = False
        , asLookup = Just (`Map.lookup` xs)
        , asDictItems = Just $ Map.toAscList xs
        }
{-# INLINE mapToGVal #-}

I changed rawJSONToGVal to transform the keys and values in a single pass.

#if MIN_VERSION_aeson(2,0,0)
rawJSONToGVal (JSON.Object o) = mapToGVal . Map.fromList . List.map (bimap AK.toText toGVal) $ AKM.toList o
#else
rawJSONToGVal (JSON.Object o) = toGVal o
#endif

This change does indeed improve the performance.

toGVal (ms)
Aeson 2 Map 10.80
Aeson 2 HashMap 10.81

Next, I created a second version of unsafeAesonToGVal (code) that avoids calling toGVal at all. This is done by copying instance implementations into separate functions, with slight modifications. This allowed me to avoid multiple traversals as well as do some aggressive inlining, but the code is even more unsightly than the first version. This version even marginally outperforms the Aeson 1 baseline in this test case.

toGVal unsafeAesonToGVal 1 unsafeAesonToGVal 2
Aeson 1 10.94 11.01 11.19
Aeson 2 Map 10.95 10.79 10.78
Aeson 2 HashMap 10.90 10.84 10.76