packages feed

json-syntax 0.2.2.0 → 0.2.3.0

raw patch · 6 files changed

+225/−15 lines, 6 filesdep −integer-gmpdep ~basedep ~byteslicedep ~primitivePVP ok

version bump matches the API change (PVP)

Dependencies removed: integer-gmp

Dependency ranges changed: base, byteslice, primitive

API changes (from Hackage documentation)

+ Json.Flatten: flatten :: Char -> Value -> Value

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for json-syntax +## 0.2.3.0 -- 2022-03-22++* Add `Json.Flatten` module.+* Drop support for GHCs older than 9.0.+* Replace integer-gmp with ghc-bignum.+ ## 0.2.2.0 -- 2022-07-15  * Build with GHC 9.2.3.
+ common/Person.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module Person+  ( encodedPerson+  , encodedFlattenedPerson+  ) where++import Data.ByteString (ByteString)+import Data.ByteString.Short (ShortByteString,toShort)+import Data.Primitive (ByteArray)+import Data.Text.Encoding (encodeUtf8)+import NeatInterpolation (text)++import qualified Data.Primitive as PM+import qualified Data.ByteString.Short.Internal as BSS++shortByteStringToByteArray :: ShortByteString -> ByteArray +shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x++encodedPerson :: ByteArray+encodedPerson =+  shortByteStringToByteArray (toShort byteStringPerson)++encodedFlattenedPerson :: ByteArray+encodedFlattenedPerson =+  shortByteStringToByteArray (toShort byteStringFlattenedPerson)++byteStringPerson :: ByteString+byteStringPerson = encodeUtf8+  [text|+    { "name": "bilbo"+    , "occupation":+      { "name": "burglar"+      , "start": "2022-05-30"+      }+    , "height": 124+    , "favorites": ["adventures","lunch"]+    }+  |]++byteStringFlattenedPerson :: ByteString+byteStringFlattenedPerson = encodeUtf8+  [text|+    { "name": "bilbo"+    , "occupation.name": "burglar"+    , "occupation.start": "2022-05-30"+    , "height": 124+    , "favorites": ["adventures","lunch"]+    }+  |]+
json-syntax.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: json-syntax-version: 0.2.2.0+version: 0.2.3.0 synopsis: High-performance JSON parser and encoder description:   This library parses JSON into a @Value@ type that is consistent with the@@ -28,19 +28,19 @@ library   exposed-modules:     Json+    Json.Flatten     Json.Smile   build-depends:     , array-builder >=0.1 && <0.2     , array-chunks >=0.1.3 && <0.2-    , base >=4.12 && <5+    , base >=4.15 && <5     , bytebuild >=0.3.10 && <0.4-    , byteslice >=0.1.3 && <0.3+    , byteslice >=0.2.9 && <0.3     , bytesmith >=0.3.8 && <0.4     , bytestring >=0.10.8 && <0.12-    , integer-gmp >=1.0 && <1.2     , natural-arithmetic >=0.1.2 && <0.2     , contiguous >=0.6 && <0.7-    , primitive >=0.7 && <0.8+    , primitive >=0.7 && <0.10     , run-st >=0.1.1 && <0.2     , scientific-notation >=0.1.5 && <0.2     , text-short >=0.1.3 && <0.2@@ -57,6 +57,7 @@   main-is: Main.hs   other-modules:     Twitter100+    Person   ghc-options: -Wall -O2   build-depends:     , QuickCheck >=2.14.2
+ src/Json/Flatten.hs view
@@ -0,0 +1,121 @@+{-# language BangPatterns #-}+{-# language DuplicateRecordFields #-}+{-# language NamedFieldPuns #-}++-- | Flatten nested JSON objects into a single JSON object in which the keys+-- have been joined by the separator.+module Json.Flatten+  ( flatten+  ) where++import Control.Monad.ST (ST)+import Control.Monad.ST.Run (runByteArrayST)+import Data.Builder.Catenable (Builder)+import Data.ByteString.Short.Internal (ShortByteString(SBS))+import Data.Text.Short (ShortText)+import Data.Primitive (SmallArray,ByteArray(ByteArray),MutableByteArray)+import Data.Word (Word8)+import Json (Member(Member))+import qualified Json+import qualified Data.Chunks as Chunks+import qualified Data.Primitive.Contiguous as C+import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Text.Utf8 as Utf8+import qualified Data.Primitive as PM+import qualified Data.Builder.Catenable as Builder+import qualified Data.Text.Short as TS+import qualified Data.Text.Short.Unsafe as TS++-- | Flatten a json value, recursively descending into objects and joining+-- keys with the separator. For example:+--+-- > { "name": "bilbo"+-- > , "occupation":+-- >   { "name": "burglar"+-- >   , "start": "2022-05-30"+-- >   }+-- > , "height": 124+-- > , "favorites": ["adventures","lunch"]+-- > }+--+-- Becomes:+--+-- > { "name": "bilbo"+-- > , "occupation.name": "burglar"+-- > , "occupation.start": "2022-05-30"+-- > , "height": 124+-- > , "favorites": ["adventures","lunch"]+-- > }+--+-- Currently, the implementation of this function throws an exception if+-- any separator other than period is used. This may be corrected in a future+-- release.+flatten :: Char -> Json.Value -> Json.Value+flatten c v = case c of+  '.' -> flattenPeriod v+  _ -> errorWithoutStackTrace "Json.Flatten.flatten: only period is supported"++-- built backwards+data ShortTexts+  = ShortTextsCons !ShortText !ShortTexts+  | ShortTextsBase !ShortText++flattenPeriod :: Json.Value -> Json.Value+flattenPeriod x = case x of+  Json.Object mbrs ->+    let bldr = foldMap (\Member{key,value} -> flattenPrefix (ShortTextsBase key) value) mbrs+        chunks = Builder.run bldr+        result = Chunks.concat chunks+     in Json.Object result+  Json.Array ys -> Json.Array $! C.map' flattenPeriod ys+  _ -> x++flattenPrefix ::+     ShortTexts -- context accumulator+  -> Json.Value+  -> Builder Json.Member+flattenPrefix !pre x = case x of+  Json.Object mbrs -> flattenObject pre mbrs+  _ ->+    let !a = flattenPeriod x+        !k = runShortTexts pre+        !mbr = Json.Member{key=k,value=a}+     in Builder.Cons mbr Builder.Empty++flattenObject :: ShortTexts -> SmallArray Json.Member -> Builder Json.Member+flattenObject !pre !mbrs = foldMap+  (\Member{key,value} -> flattenPrefix (ShortTextsCons key pre) value+  ) mbrs++runShortTexts :: ShortTexts -> ShortText+runShortTexts !ts0 = go 0 ts0+  where+  paste :: MutableByteArray s -> Int -> ShortTexts -> ST s ByteArray+  paste !dst !ix (ShortTextsBase t) =+    let len = Bytes.length (Utf8.fromShortText t)+     in case ix - len of+          0 -> do+            PM.copyByteArray dst 0 (st2ba t) 0 len+            PM.unsafeFreezeByteArray dst+          _ -> errorWithoutStackTrace "Json.Flatten.runShortTexts: implementation mistake"+  paste !dst !ix (ShortTextsCons t ts) = do+    let !len = Bytes.length (Utf8.fromShortText t)+    let !ixNext = ix - len+    PM.copyByteArray dst ixNext (st2ba t) 0 len+    let !ixPred = ixNext - 1+    PM.writeByteArray dst ixPred (0x2E :: Word8)+    paste dst ixPred ts+  go :: Int -> ShortTexts -> ShortText+  go !byteLenAcc (ShortTextsCons t ts) =+    go (Bytes.length (Utf8.fromShortText t) + byteLenAcc + 1) ts+  go !byteLenAcc (ShortTextsBase t) =+    let !(ByteArray r) = runByteArrayST $ do+          let totalLen = Bytes.length (Utf8.fromShortText t) + byteLenAcc+          dst <- PM.newByteArray totalLen+          paste dst totalLen ts0+     in TS.fromShortByteStringUnsafe (SBS r)++st2ba :: ShortText -> ByteArray+{-# inline st2ba #-}+st2ba t = case TS.toShortByteString t of+  SBS x -> ByteArray x
src/Json/Smile.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}+{-# LANGUAGE UnboxedTuples #-}  module Json.Smile   ( -- * Encode JSON Document@@ -35,6 +36,8 @@ import Data.Text.Short (ShortText) import Data.Word (Word8,Word32,Word64) import Data.Word.Zigzag (toZigzag32,toZigzag64)+import GHC.Exts (RealWorld,Word#,State#)+import GHC.IO (IO(IO)) import GHC.Word (Word(..)) import Json (Value(..), Member(..)) import Numeric.Natural (Natural)@@ -49,7 +52,8 @@ import qualified Data.Number.Scientific as Sci import qualified Data.Text.Short as TS import qualified GHC.Exts as Exts-import qualified GHC.Integer.GMP.Internals as GMP+import qualified GHC.Num.BigNat as BN+import qualified GHC.Num.Integer as Integer import qualified Prelude  -- | Encode a Json 'Value' to the Smile binary format.@@ -118,11 +122,11 @@ integerToBase256ByteArray c = if c == 0   then byteArrayFromListN 1 [0::Word8]   else case c of-    GMP.Jp# bn -> unsafeDupablePerformIO $ do-      let nDigits256 = fromIntegral @Word @Int (W# (GMP.sizeInBaseBigNat bn 256#))+    Integer.IP bn -> unsafeDupablePerformIO $ do+      let nDigits256 = fromIntegral @Word @Int (W# (BN.bigNatSizeInBase# 256## bn))       mut <- newByteArray nDigits256       let !(MutableByteArray mut#) = mut-      !_ <- GMP.exportBigNatToMutableByteArray bn mut# 0## 1#+      !_ <- liftWordIO (BN.bigNatToMutableByteArray# bn mut# 0## 1# )       -- This is safe because Jp cannot have zero inside it.       w0 :: Word8 <- readByteArray mut 0       if testBit w0 7@@ -134,17 +138,24 @@           copyMutableByteArray dst 1 mut 0 nDigits256           unsafeFreezeByteArray dst         else unsafeFreezeByteArray mut-    GMP.Jn# bn -> twosComplementBigNat bn-    GMP.S# i -> case i Exts.># 0# of+    Integer.IN bn -> twosComplementBigNat bn+    Integer.IS i -> case i Exts.># 0# of       1# -> encodePosWordBase256 (W# (Exts.int2Word# i))       _ -> encodeNegWordBase256 (W# (Exts.int2Word# i)) -twosComplementBigNat :: GMP.BigNat -> ByteArray+liftWordIO :: (State# RealWorld -> (# State# RealWorld, Word# #)) -> IO Word+{-# inline liftWordIO #-}+liftWordIO f = IO+  (\s -> case f s of+    (# s', w #) -> (# s', W# w #)+  )++twosComplementBigNat :: BN.BigNat# -> ByteArray twosComplementBigNat bn = unsafeDupablePerformIO $ do-  let nDigits256 = fromIntegral @Word @Int (W# (GMP.sizeInBaseBigNat bn 256#))+  let nDigits256 = fromIntegral @Word @Int (W# (BN.bigNatSizeInBase# 256## bn))   mut <- newByteArray nDigits256   let !(MutableByteArray mut#) = mut-  !_ <- GMP.exportBigNatToMutableByteArray bn mut# 0## 1#+  !_ <- liftWordIO (BN.bigNatToMutableByteArray# bn mut# 0## 1# )   -- First, complement   let goComplement !ix = if ix >= 0         then do
test/Main.hs view
@@ -1,13 +1,16 @@ {-# language LambdaCase #-}+{-# language MultiWayIf #-} {-# language OverloadedStrings #-} {-# language ScopedTypeVariables #-}  import Control.Monad (when)-import Data.Bytes (Bytes) import Data.ByteString.Short.Internal (ShortByteString(SBS))+import Data.Bytes (Bytes) import Data.Primitive (ByteArray(ByteArray)) import Data.Scientific (Scientific,scientific) import Data.Text.Short (ShortText)+import Json.Flatten (flatten)+import Person (encodedPerson,encodedFlattenedPerson) import System.IO (withFile,IOMode(..)) import Test.QuickCheck ((===)) import Test.Tasty (defaultMain,testGroup,TestTree)@@ -109,6 +112,16 @@         case J.decode enc of           Left e -> QC.counterexample (show e) False           Right val1 -> val0 === val1+    , THU.testCase "Q" $+        -- Test that Unicode <right single quotation mark> is decoded correctly.+        Right (J.String "It\2019s over now")+        @=?+        J.decode (shortTextToBytes "\"It\2019s over now\"")+    , THU.testCase "R" $+        -- Test that Unicode <right single quotation mark> is encoded correctly.+        shortTextToBytes "\"It\2019s over now\""+        @=?+        BChunks.concat (Builder.run 4 (J.encode (J.String "It\2019s over now")))     ]   , testGroup "smile-fragment"     [ THU.testCase "biginteger-65535" $@@ -207,6 +220,13 @@         Right j -> case J.decode (BChunks.concat (Builder.run 1 (J.encode j))) of           Left _ -> fail "encode did not produce a document that could be decoded"           Right j' -> when (j /= j') (fail "document was not the same after roundtrip")+  , testGroup "flatten"+      [ THU.testCase "Person" $ +          if | Right original <- J.decode (Bytes.fromByteArray encodedPerson)+             , Right flattened <- J.decode (Bytes.fromByteArray encodedFlattenedPerson)+               -> flattened @=? flatten '.' original+             | otherwise -> fail "bad input from common/Person.hs"+      ]   ]  jsonFromPrintableStrings :: [QC.PrintableString] -> J.Value