packages feed

sajson (empty) → 0.1.0.0

raw patch · 8 files changed

+605/−0 lines, 8 filesdep +QuickCheckdep +aesondep +basesetup-changed

Dependencies added: QuickCheck, aeson, base, bytestring, criterion, hspec, sajson, scientific, text, unordered-containers, vector

Files

+ LICENSE view
@@ -0,0 +1,49 @@+Written by Zhouyu Qian.+Copyright (c) 2017 Zhouyu Qian++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use, copy,+modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.++---++The software incorporates the sajson C++ library.++Written by Chad Austin+Copyright (c) 2012-2017 Chad Austin++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use, copy,+modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,69 @@+# sajson++This is a Haskell package that is a thin wrapper over the wonderful+[sajson](https://github.com/chadaustin/sajson) library written by Chad Austin.+It provides a high performance JSON parser written in C++11. After parsing, a+Haskell `Data.Aeson.Value` is constructed.++## Who This Is For++This library is specifically designed for Haskell users who find the performance+of the pure-Haskell `Data.Aeson` parser inadequate. This library focuses on+performance, not portability or absolute correctness. You should *only* use this+library if all of the following is true:++*  You are using a system that is 64-bit. This library assumes+   `sizeof(size_t) == 8`. If this is not the case, the behavior is+   undefined.++*  You are using a system where the alignment of `double` is no stricter than+   that of `size_t`. Internally, the library casts a pointer to `size_t` into a+   pointer to `double` and expects this to be meaningful.++*  You do not expect to parse floating point numbers beyond the range of+   `double`.++*  You do not expect to parse integers that are longer than 53 bits.++*  You do not expect parsing a JSON number will produce a Haskell `Scientific`+   value that exactly represents the mathematical value of the JSON number, or+   even the closest approximation in IEEE-754 `double`.++   For example if you parse numbers that would be denormal numbers+   in IEEE-754 double-precision numbers, you will very likely get back zero+   instead.++   As another example, if you parse `0.3`, which cannot be represented+   exactly in IEEE-754 `double`, you will get back 0.30000000000000004, instead+   of the closest number in IEEE-754 `double` which is 0.29999999999999999.++*  You wish to sacrifice memory consumption for speed. The *intermediate* memory+   needed is 9 times the number of bytes of the input `ByteString` plus+   constant. This excludes the memory needed by the parsed `Value` or the memory+   of the input `ByteString`.++*  You are parsing JSON that is known to be valid. As a library written in C++,+   it is inherently unsafe. Despite the use of address sanitizer and AFL which+   have+   [caught memory usage bugs](https://github.com/chadaustin/sajson/commit/0bd8a661421fc3e61262c283543689b0f8a88483),+   it is difficult to guarantee that creatively crafted input will not crash or+   cause arbitrary code execution. It is recommend that you use this library+   only for parsing known good JSON.++If you satisfy all of the above, then this library could work for you. Again,+please benchmark and show that JSON parsing is indeed a bottleneck before using+this library.++## Ideas and Future Plan++*  Skip creating `Value`s when the you eventually want another Haskell type.+   Currently the `FromJSON` instance needs a `Value`, but perhaps we can devise+   another method to avoid the generation of these intermediate `Value`s.++*  Provide a benchmark so that we can quantitatively argue this library parses+   JSON faster than `Data.Aeson`.++*  Provide an alternative for parsing floating point numbers that better+   preserves the value.++*  More complete tests, including QuickCheck-based tests.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bench/Bench.hs view
@@ -0,0 +1,34 @@+module Main where++import Control.Monad+import Criterion.Main+import Data.Aeson as A+import qualified Data.ByteString as B+import Data.Sajson as S+import System.Environment++dataFiles :: [String]+dataFiles =+  [+    "apache_builds.min.json",+    "github_events.min.json",+    "instruments.min.json",+    "mesh.min.json",+    "svg_menu.min.json",+    "twitter.min.json",+    "update-center.min.json"+  ]++parseWithAeson, parseWithSajson :: B.ByteString -> Maybe Value+parseWithAeson = A.decodeStrict'+parseWithSajson = S.decodeStrict++main :: IO ()+main = do+  mdir <- lookupEnv "SAJSON_BENCHMARK_DATA_DIR"+  let dir = maybe "./" (++"/") mdir+  benches <- forM dataFiles $ \f -> do+    let path = dir ++ f+    contents <- B.readFile path+    pure $ bgroup f [ bench "aeson" $ nf parseWithAeson contents, bench "sajson" $ nf parseWithSajson contents ]+  defaultMain benches
+ cbits/sajson_wrapper.cpp view
@@ -0,0 +1,60 @@+#include "sajson_wrapper.h"+#include "sajson.hpp"+#include <stdlib.h>+#include <type_traits>++// never instantiated, only inherits so static_cast is legal+struct sajson_document: sajson::document {};+struct sajson_value: sajson::value {};++static inline sajson::document* unwrap(sajson_document* doc) {+    return static_cast<sajson::document*>(doc);+}++static inline sajson_document* wrap(sajson::document* doc) {+    return static_cast<sajson_document*>(doc);+}++size_t sajson_document_sizeof(void) {+    return sizeof(sajson::document);+}++sajson_document* sajson_parse_single_allocation(char* str, size_t length, size_t *buffer, char *rv) {+    auto doc = sajson::parse(sajson::single_allocation(buffer, length), sajson::mutable_string_view(length, str));+    return wrap(new(rv) sajson::document(std::move(doc)));+}++void sajson_free_document(sajson_document* doc) {+    static_assert(std::is_trivially_destructible<sajson::document>::value, "sajson::document needs to have a trivial destructor because we do not call it; we just deallocate its storage");+    // According to the C++ Standard (section 3.8), you can end an object's lifetime by deallocating or reusing its storage. There's no need to call a destructor that does nothing. In this case we statically verify that it is trivially destructible.+    // unwrap(doc)->~document();+}++int sajson_has_error(sajson_document* doc) {+    return !unwrap(doc)->is_valid();+}++size_t sajson_get_error_line(sajson_document* doc) {+    return unwrap(doc)->get_error_line();+}++size_t sajson_get_error_column(sajson_document* doc) {+    return unwrap(doc)->get_error_column();+}++const char* sajson_get_error_message(sajson_document* doc) {+    return unwrap(doc)->get_error_message_as_cstring();+}++uint8_t sajson_get_root_type(sajson_document* doc) {+    return unwrap(doc)->_internal_get_root_type();+}++const size_t* sajson_get_root(sajson_document* doc) {+    return unwrap(doc)->_internal_get_root();+}++const unsigned char* sajson_get_input(sajson_document* doc) {+    return reinterpret_cast<const unsigned char*>(+        unwrap(doc)->_internal_get_input().get_data());+}
+ sajson.cabal view
@@ -0,0 +1,74 @@+name: sajson+version: 0.1.0.0+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE+copyright: (c) 2012-2017 Chad Austin+           (c) 2017 Zhouyu Qian+maintainer: qzy@qzy.io+stability: experimental+homepage: https://github.com/kccqzy/haskell-sajson#readme+synopsis: Fast JSON parsing powered by Chad Austin's sajson library+description:+    A fast JSON parsing library that is faster than aeson.+category: Web, Text, JSON+author: Chad Austin, Zhouyu Qian+tested-with: GHC ==8.0.2+extra-source-files:+    README.md++source-repository head+    type: git+    location: https://github.com/kccqzy/haskell-sajson++library+    exposed-modules:+        Data.Sajson+    build-depends:+        base >=4.7 && <5,+        bytestring >=0.10.8.1 && <0.11,+        text >=1.2.2.1 && <1.3,+        aeson >=1.0.2.1 && <1.1,+        vector >=0.11.0.0 && <0.12,+        scientific >=0.3.4.12 && <0.4,+        unordered-containers >=0.2.8.0 && <0.3+    cc-options: -Wall -O3 -march=native -std=c++11 -fomit-frame-pointer+    c-sources:+        cbits/sajson_wrapper.cpp+    default-language: Haskell2010+    extra-libraries:+        stdc+++    include-dirs: cbits+    hs-source-dirs: src+    ghc-options: -Wall -O2++executable sajson-bench+    main-is: Bench.hs+    build-depends:+        base >=4.9.1.0 && <5,+        sajson,+        criterion >=1.1.4.0 && <1.2,+        aeson >=1.0.2.1 && <1.1,+        bytestring >=0.10.8.1 && <0.11+    default-language: Haskell2010+    hs-source-dirs: bench+    ghc-options: -Wall -threaded -rtsopts++test-suite sajson-test+    type: exitcode-stdio-1.0+    main-is: Spec.hs+    build-depends:+        base >=4.9.1.0 && <5,+        sajson,+        QuickCheck >=2.9.2 && <2.10,+        aeson >=1.0.2.1 && <1.1,+        bytestring >=0.10.8.1 && <0.11,+        hspec >=2.4.3 && <2.5,+        scientific >=0.3.4.12 && <0.4,+        text >=1.2.2.1 && <1.3,+        unordered-containers >=0.2.8.0 && <0.3,+        vector >=0.11.0.0 && <0.12+    default-language: Haskell2010+    hs-source-dirs: test+    ghc-options: -Wall -threaded -rtsopts
+ src/Data/Sajson.hs view
@@ -0,0 +1,235 @@+{-# LANGUAGE ForeignFunctionInterface #-}++-- |+-- Module:      Data.Sajson+-- Copyright:   (c) 2017 Zhouyu Qian+--              (c) 2012-2017 Chad Austin+-- License:     MIT+-- Maintainer:  Zhouyu Qian+--+-- A more efficient C++-based JSON parser.++module Data.Sajson+  ( -- * Introduction+    -- $introduction++    -- * Native APIs+    -- $native+    sajsonParse+  , unsafeMutableByteStringSajsonParse+  , SajsonParseError(..)++    -- * Drop-in Replacements+    -- $replacements+  , eitherDecodeStrict+  , decodeStrict+  , eitherDecode+  , decode+  ) where++import Control.Exception+import Control.Monad+import Data.Aeson.Types (FromJSON (..), Value (..), parseEither, parseMaybe)+import Data.Bits+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Unsafe as BU+import qualified Data.HashMap.Strict as HM+import Data.Scientific+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.Vector as V+import Data.Word+import Foreign.C.String+import Foreign.C.Types+import Foreign.Marshal.Alloc+import Foreign.Ptr+import Foreign.Storable+import System.IO.Unsafe++-- $introduction+-- This library is a high-performance parser for JSON. It is an alternative+-- to the native parsers in 'Data.Aeson'.+--+-- Before using this library, please carefully consider whether you actually+-- should use this library. This library is specifically designed for+-- Haskell users who find the performance of the pure-Haskell 'Data.Aeson'+-- parser inadequate. This library focuses on performance, not portability+-- or absolute correctness. You should /only/ use this library if all of the+-- following is true:+--+--   *  You are using a system that is 64-bit.+--   *  You are using a system where the alignment of @double@ is no stricter than+--      that of @size_t@.+--   *  You do not expect to parse floating point numbers beyond the range of+--      `double`.+--   *  You do not expect to parse integers that are longer than 53 bits.+--   *  You do not expect parsing a JSON number will produce a Haskell+--      'Data.Scientific.Scientific' value that exactly represents the+--      mathematical value of the JSON number, or even the closest+--      approximation in IEEE-754 `double`.+--   *  You wish to sacrifice memory consumption for speed.+--   *  You are parsing JSON that is known to be valid.+--+-- When all of the above are true, this library could work for you. Again,+-- please benchmark and show that JSON parsing is indeed a bottleneck before+-- using this library.++-- $native+-- A single function 'sajsonParse' is provided to access the parsing+-- functionality directly. However, you might instead want to use one of the+-- drop-in replacements below.++-- | Phantom type for a pointer to sajson_document.+data SajsonDocument++-- | A structure containing information about parse failures. The error line,+-- column, and messages are from the @sajson@ C++ library.+data SajsonParseError+  = SajsonParseError+  { sajsonParseErrorLine :: Int -- ^ The line where the error occurred.+  , sajsonParseErrorColumn :: Int -- ^ The column where the error occurred.+  , sajsonParseErrorMessage :: String -- ^ The error message. This is a canned message; it is not generated from the input.+  } deriving (Show, Eq)++type SajsonValueType = Word8+type SajsonValuePayload = Ptr CSize+type SajsonValueInputMutableView = Ptr CUChar++foreign import ccall unsafe "sajson_wrapper.h sajson_document_sizeof"+  c_sajson_document_sizeof :: CSize++foreign import ccall unsafe "sajson_wrapper.h sajson_parse_single_allocation"+  c_sajson_parse_single_allocation :: Ptr CChar -> CSize -> Ptr CSize -> Ptr CChar -> IO (Ptr SajsonDocument)++foreign import ccall unsafe "sajson_wrapper.h sajson_has_error"+  c_sajson_has_error :: Ptr SajsonDocument -> IO CInt++foreign import ccall unsafe "sajson_wrapper.h sajson_get_error_line"+  c_sajson_get_error_line :: Ptr SajsonDocument -> IO CSize++foreign import ccall unsafe "sajson_wrapper.h sajson_get_error_column"+  c_sajson_get_error_column :: Ptr SajsonDocument -> IO CSize++foreign import ccall unsafe "sajson_wrapper.h sajson_get_error_message"+  c_sajson_get_error_message :: Ptr SajsonDocument -> IO CString++foreign import ccall unsafe "sajson_wrapper.h sajson_get_root_type"+  c_sajson_get_root_type :: Ptr SajsonDocument -> IO SajsonValueType++foreign import ccall unsafe "sajson_wrapper.h sajson_get_root"+  c_sajson_get_root :: Ptr SajsonDocument -> IO SajsonValuePayload++foreign import ccall unsafe "sajson_wrapper.h sajson_get_input"+  c_sajson_get_input :: Ptr SajsonDocument -> IO SajsonValueInputMutableView++-- | Parse a 'CStringLen' into a 'Value' with the possibility of failing with+-- 'SajsonParseError'.+doParse :: CStringLen -> IO (Either SajsonParseError Value)+doParse (ptr, size) =+  allocaBytes (fromIntegral c_sajson_document_sizeof) $ \rvbuf ->+  allocaBytes (8 * size) $ \buf -> do+  doc <- c_sajson_parse_single_allocation ptr (fromIntegral size) buf rvbuf+  hasError <- c_sajson_has_error doc+  case hasError of+    0 -> Right <$> join (constructHaskellValue+                         <$> c_sajson_get_root_type doc+                         <*> c_sajson_get_root doc+                         <*> c_sajson_get_input doc)+    _ -> Left <$> (SajsonParseError+                   <$> (fromIntegral <$> c_sajson_get_error_line doc)+                   <*> (fromIntegral <$> c_sajson_get_error_column doc)+                   <*> (c_sajson_get_error_message doc >>= peekCString))+{-# INLINE doParse #-}++-- | Parse a 'B.ByteString' into a 'Value' with the possibility of failing with+-- 'SajsonParseError'.+sajsonParse :: B.ByteString -> IO (Either SajsonParseError Value)+sajsonParse bs = B.useAsCStringLen bs doParse++unsafeMutableByteStringSajsonParse :: B.ByteString -> IO (Either SajsonParseError Value)+unsafeMutableByteStringSajsonParse bs = BU.unsafeUseAsCStringLen bs doParse++makeString :: SajsonValuePayload -> Int -> SajsonValueInputMutableView -> IO T.Text+makeString payload index buf = do+  start <- peekElemOff payload index+  end <- peekElemOff payload (1 + index)+  bs <- B.packCStringLen (buf `plusPtr` fromIntegral start, fromIntegral (end - start))+  pure (TE.decodeUtf8 bs)+{-# INLINE makeString #-}++makeValue :: SajsonValuePayload -> Int -> SajsonValueInputMutableView -> IO Value+makeValue payload index buf = do+    element <- peekElemOff payload index+    let vt = fromIntegral (element .&. 7)+        vp = payload `plusPtr` fromIntegral (8 * (element `shiftR` 3)) -- where 8 == sizeof(size_t)+    constructHaskellValue vt vp buf++constructHaskellValue :: SajsonValueType -> SajsonValuePayload -> SajsonValueInputMutableView -> IO Value+constructHaskellValue 0 payload _ = do -- TYPE_INTEGER+  i <- peekElemOff (castPtr payload) 0+  pure (Number (fromIntegral (i :: CInt)))+constructHaskellValue 1 payload _ = do -- TYPE_DOUBLE+  d <- peekElemOff (castPtr payload) 0+  pure (Number (fromFloatDigits (d :: CDouble)))+constructHaskellValue 2 _ _ = -- TYPE_NULL+  pure Null+constructHaskellValue 3 _ _ = -- TYPE_FALSE+  pure (Bool False)+constructHaskellValue 4 _ _ = -- TYPE_TRUE+  pure (Bool True)+constructHaskellValue 5 payload buf = -- TYPE_STRING+  String <$> makeString payload 0 buf+constructHaskellValue 6 payload buf = do -- TYPE_ARRAY+  len <- peekElemOff payload 0+  (Array <$>) . V.generateM (fromIntegral len) $ \i -> makeValue payload (1 + i) buf+constructHaskellValue 7 payload buf = do -- TYPE_OBJECT+  len <- peekElemOff payload 0+  Object <$> V.foldM' go HM.empty (V.enumFromN 0 (fromIntegral len))+  where go hm i = do+          key <- makeString payload (1 + i * 3) buf+          val <- makeValue payload (3 + i * 3) buf+          pure $ HM.insert key val hm+constructHaskellValue vt _ _ = throwIO (ErrorCall $ "Data.Sajson internal error: unexpected sajson type tag: " ++ show vt)++-- $replacements+-- These functions are (almost) drop-in replacements for the equivalently named+-- functions in 'Data.Aeson', with the caveat that the outermost structure must+-- be either an 'Object' or an 'Array'.++toDataEither :: FromJSON a => Value -> Either String a+toDataEither = parseEither parseJSON++toData :: FromJSON a => Value -> Maybe a+toData = parseMaybe parseJSON++describeSajsonParseError :: SajsonParseError -> String+describeSajsonParseError (SajsonParseError line col err) = "Error in sajson parser: line " ++ show line ++ " column " ++ show col ++ ": " ++ err++-- | Efficiently deserialize a JSON value from a strict 'B.ByteString'. If this+-- fails, 'Left' is returned with a 'String' describing the issue.+eitherDecodeStrict :: FromJSON a => B.ByteString -> Either String a+eitherDecodeStrict bs = unsafePerformIO $ either (Left . describeSajsonParseError) toDataEither <$> sajsonParse bs++-- | Efficiently deserialize a JSON value from a strict 'B.ByteString'. If this+-- fails, 'Nothing' is returned.+decodeStrict :: FromJSON a => B.ByteString -> Maybe a+decodeStrict bs = unsafePerformIO $ either (pure Nothing) toData <$> sajsonParse bs++-- | Deserialize a JSON value from a lazy 'BL.ByteString'. If this fails, 'Left'+-- is returned with a 'String' describing the issue.+--+-- It should be noted that this function first converts the lazy 'BL.ByteString'+-- into a strict 'B.ByteString'. It does not allow streaming. Therefore it+-- requires at least twice the total size of the 'BL.ByteString' to be in memory.+eitherDecode :: FromJSON a => BL.ByteString -> Either String a+eitherDecode = eitherDecodeStrict . BL.toStrict++-- | Deserialize a JSON value from a lazy 'BL.ByteString'. If this+-- fails, 'Nothing' is returned.+--+-- It should be noted that this function first converts the lazy 'BL.ByteString'+-- into a strict 'B.ByteString'. It does not allow streaming. Therefore it+-- requires at least twice the total size of the 'BL.ByteString' to be in memory.+decode :: FromJSON a => BL.ByteString -> Maybe a+decode = decodeStrict . BL.toStrict
+ test/Spec.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++module Main where++import Data.Aeson as A+import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.HashMap.Strict as HM+import Data.Sajson as S+import Data.Scientific+import Data.String+import qualified Data.Text as T+import qualified Data.Vector as V+import Test.Hspec+import Test.QuickCheck++newtype AcceptableValue = AcceptableValue { getAcceptableValue :: Value } deriving (Show, Eq)++instance Arbitrary AcceptableValue where+  arbitrary = AcceptableValue <$> oneof [Array <$> genArray, Object <$> genObject]+      where genText = T.pack <$> listOf (oneof [choose (' ', '~'), choose ('\256', '\383'), choose ('\19968', '\40959'), choose ('\128512', '\128591')]) -- latin text, CJK ideographs, and emoji+            genNumber = fromIntegral <$> choose (-9007199254740992, 9007199254740992 :: Int)+            genArray = V.fromList <$> reducedSize (listOf genValue)+            genObject = HM.fromList <$> reducedSize (listOf ((,) <$> genText <*> genValue))+            genValue = oneof [Number <$> genNumber, pure Null, Bool <$> arbitrary, String <$> genText, Array <$> genArray, Object <$> genObject]+            reducedSize = scale (`div` 2)++main :: IO ()+main = hspec $ do+  describe "sajsonParse" $ do+    it "returns proper error for truncated JSON document" $ do+      r <- sajsonParse "{\"abc\":123,\"def\":[1,2,3"+      r `shouldBe` Left SajsonParseError {sajsonParseErrorLine = 1, sajsonParseErrorColumn = 24, sajsonParseErrorMessage = "unexpected end of input"}+    describe "returns Right for correct JSON document" $ do+      it "object of null" $ do+        r <- sajsonParse "{\"abc\":null}"+        r `shouldBe` Right (object ["abc" .= Null])+      it "object of true" $ do+        r <- sajsonParse "{\"abc\":true}"+        r `shouldBe` Right (object ["abc" .= Bool True])+      it "object of false" $ do+        r <- sajsonParse "{\"abc\":false}"+        r `shouldBe` Right (object ["abc" .= Bool False])+      it "object of integer" $ do+        r <- sajsonParse "{\"abc\":2147483647}"+        r `shouldBe` Right (object ["abc" .= Number 2147483647])+      it "object of double" $ do+        r <- sajsonParse "{\"abc\":3.141592653589793}"+        r `shouldBe` Right (object ["abc" .= Number 3.141592653589793])+      it "object of big integer (exponential notation)" $ do+        r <- sajsonParse "{\"abc\":1e308}"+        r `shouldBe` Right (object ["abc" .= Number (scientific 1 308)])+      it "object of array of number" $ do+        r <- sajsonParse "{\"abc\":[1,2,3],\"def\":[0.1,0.2]}"+        r `shouldBe` Right (object ["abc" .= [1.0 :: Scientific, 2.0, 3.0], "def" .= [0.1 :: Scientific, 0.2]])+    it "round trips acceptable values with aeson" $ property $ \(AcceptableValue v) -> ioProperty $ do+      let encoded = encode v+      decoded <- sajsonParse (BL.toStrict encoded)+      decoded `shouldBe` Right v+      pure True++  let testEither :: IsString s => (forall a. FromJSON a => s -> Either String a) -> (forall a. FromJSON a => s -> Either String a) -> String -> Spec+      testEither f f' n = describe n $ do+        it "handles incorrect JSON" $+          f "[0" `shouldBe` (Left "Error in sajson parser: line 1 column 3: unexpected end of input" :: Either String [Int])+        it "handles correct JSON but incorrect schema" $ do+          let s = "{\"a\":42}"+          f s `shouldBe` (f' s :: Either String [Int])++      testMaybe :: IsString s => (forall a. FromJSON a => s -> Maybe a) -> String -> Spec+      testMaybe f n = describe n $ do+        it "handles incorrect JSON" $+          f "[0" `shouldBe` (Nothing :: Maybe [Int])+        it "handles correct JSON but incorrect schema" $ do+          let s = "{\"a\":42}"+          f s `shouldBe` (Nothing :: Maybe [Int])++  testEither S.eitherDecodeStrict A.eitherDecodeStrict "eitherDecodeStrict"+  testEither S.eitherDecode A.eitherDecode "eitherDecode"++  testMaybe S.decodeStrict "decodeStrict"+  testMaybe S.decode "decode"