diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/bench/Bench.hs b/bench/Bench.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench.hs
@@ -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
diff --git a/cbits/sajson_wrapper.cpp b/cbits/sajson_wrapper.cpp
new file mode 100644
--- /dev/null
+++ b/cbits/sajson_wrapper.cpp
@@ -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());
+}
diff --git a/sajson.cabal b/sajson.cabal
new file mode 100644
--- /dev/null
+++ b/sajson.cabal
@@ -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
diff --git a/src/Data/Sajson.hs b/src/Data/Sajson.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Sajson.hs
@@ -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
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -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"
