aeson-combinators 0.0.4.0 → 0.0.4.1
raw patch · 4 files changed
+129/−7 lines, 4 filesdep +criteriondep +deepseqdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: criterion, deepseq
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- CHANGELOG.md +4/−0
- README.md +7/−5
- aeson-combinators.cabal +16/−2
- benchmarks/Main.hs +102/−0
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for aeson-combinators +## 0.0.4.1 -- 2021-02-14+* Cleanup README+* CI maintanance & GHC compatibility update+ ## 0.0.4.0 -- 2020-10-24 * `Encode` module for encoding
README.md view
@@ -4,22 +4,24 @@ [](https://actions-badge.atrox.dev/turboMaCk/aeson-combinators/goto?ref=master) [](https://builtwithnix.org) -[**Low overhead**](#internals) value space `Decoder`+[**Low overhead**](#performance) value space `Decoder` on top of Aeson's Parser for combinator style decoding. This library is compatible with GHC as well as recent versions of **GHCJS**. -__Encoding to JSON is currently not supported but might be added in the future version.__- I wrote a [blob post](https://turbomack.github.io/posts/2020-02-21-value-space-decoding-for-aeson.html) describing what this library attempts to solve. -## Internals+## Performance -`Decoder a` type is a function `Value -> Parser a` the same as `fromJSON`+`Decoder a` type is a function `Value -> Parser a` the same as `parseJSON` member function of `FromJSON` class. This means there should be near zero overhead. Aeson types and functions are reused where possible. Similarly `Encoder a` type follow `toJSON` from `ToJSON` type class.++Simple benchmark shows that implementation using aeson-combinators performs better+than equivalent derived instance and on par (actually even slightly better though with difference in noise range)+with manually implemented instance. ## License
aeson-combinators.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: aeson-combinators-version: 0.0.4.0+version: 0.0.4.1 synopsis: Aeson combinators for dead simple JSON decoding description: Low overhead value space `Decoder`@@ -21,7 +21,9 @@ tested-with: GHC == 8.4.4 , GHC == 8.6.5 , GHC == 8.8.3+ , GHC == 8.8.4 , GHC == 8.10.1+ , GHC == 8.10.3 , GHCJS == 8.6.0.1 Flag doctest@@ -52,6 +54,19 @@ default-language: Haskell2010 hs-source-dirs: lib +benchmark benchmark+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: benchmarks+ build-depends: base+ , aeson+ , aeson-combinators+ , bytestring+ , criterion+ , deepseq+ , text+ ghc-options: -Wall+ default-language: Haskell2010 test-suite spec type: exitcode-stdio-1.0@@ -80,7 +95,6 @@ else build-depends: base , doctest- source-repository head type: git
+ benchmarks/Main.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++import Control.DeepSeq (NFData)+import Criterion (Benchmark, nf)+import Data.Aeson.Combinators.Decode (Decoder)+import Data.Aeson.Types (FromJSON, ToJSON, withObject,+ (.:))+import Data.ByteString.Lazy (ByteString)+import GHC.Generics (Generic)++import qualified Criterion.Main as Criterion+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Combinators.Decode as Decode+++bench :: NFData b => String -> (a -> b) -> (Int -> a) -> [Benchmark]+bench name f gen = fmap (\i -> let !n = 10 ^ i+ !generated = gen n+ in Criterion.bench (name <> " " <> show n) $ (nf f) generated+ ) ([1..4] :: [Int])+{-# INLINE bench #-}+++main :: IO ()+main =+ Criterion.defaultMain+ [ Criterion.bgroup "Combinators decoder nested" $+ bench "nested" (Decode.decode deeplyNestedDecoder) deeplyNestedValue+ , Criterion.bgroup "Derived (generic) decoder nested" $+ bench "nested" (Aeson.decode @ DeeplyNested) deeplyNestedValue+ , Criterion.bgroup "Implemented instance decoder nested" $+ bench "nested" (Aeson.decode @ DeeplyNested') deeplyNestedValue+ , Criterion.bgroup "Combinators decoder narrow" $+ bench "narrow" (Decode.decode narrowDecoder) narrowValue+ , Criterion.bgroup "Derived (generic) decoder narrow" $+ bench "narrow" (Aeson.decode @ Narrow) narrowValue+ , Criterion.bgroup "Implemented instance decoder narrow" $+ bench "narrow" (Aeson.decode @ Narrow') narrowValue+ ]+++data DeeplyNested = DeeplyNested+ { nested :: ![DeeplyNested]+ } deriving stock (Show, Generic)+ deriving anyclass (FromJSON, NFData, ToJSON)+++deeplyNestedDecoder :: Decoder DeeplyNested+deeplyNestedDecoder = DeeplyNested+ <$> Decode.key "nested" (Decode.list deeplyNestedDecoder)+++deeplyNestedValue :: Int -> ByteString+deeplyNestedValue depth = Aeson.encode $ go depth (DeeplyNested [])+ where+ go :: Int -> DeeplyNested -> DeeplyNested+ go n dn+ | n <= 0 = dn+ | otherwise = go (n - 1) $ DeeplyNested [dn]+++data DeeplyNested' = DeeplyNested'+ { nested' :: ![DeeplyNested']+ } deriving stock (Show, Generic)+ deriving anyclass (NFData)+++instance FromJSON DeeplyNested' where+ parseJSON = withObject "DeeplyNested'" $ \v ->+ DeeplyNested' <$> v .: "nested"+++data Narrow = Narrow+ { narrow :: ![Int]+ } deriving stock (Show, Generic)+ deriving anyclass (FromJSON, NFData, ToJSON)+++narrowDecoder :: Decoder Narrow+narrowDecoder = Narrow+ <$> Decode.key "narrow" (Decode.list Decode.int)+++narrowValue :: Int -> ByteString+narrowValue width = Aeson.encode $ Narrow [1..width]+++data Narrow' = Narrow'+ { narrow' :: ![Int]+ } deriving stock (Show, Generic)+ deriving anyclass (NFData)+++instance FromJSON Narrow' where+ parseJSON = withObject "narrow'" $ \v ->+ Narrow' <$> v .: "narrow"