packages feed

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 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 @@ [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FturboMaCk%2Faeson-combinators%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/turboMaCk/aeson-combinators/goto?ref=master) [![built with nix](https://builtwithnix.org/badge.svg)](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"