hermes-json 0.8.0.0 → 0.8.1.0
raw patch · 9 files changed
+115/−2 lines, 9 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Hermes: objectFold :: state -> (Text -> state -> Decoder state) -> Decoder state
+ Data.Hermes: withRawJsonByteString :: (ByteString -> Decoder a) -> Decoder a
+ Data.Hermes.Decoder.Value: objectFold :: state -> (Text -> state -> Decoder state) -> Decoder state
+ Data.Hermes.Decoder.Value: withRawJsonByteString :: (ByteString -> Decoder a) -> Decoder a
+ Data.Hermes.SIMDJSON.Bindings: getRawJSONImpl :: Value -> Ptr CString -> Ptr CSize -> IO CInt
Files
- CHANGELOG.md +11/−0
- cbits/lib.cpp +14/−0
- hermes-json.cabal +1/−1
- simdjson/singleheader/simdjson.cpp too large to diff
- simdjson/singleheader/simdjson.h too large to diff
- src/Data/Hermes.hs +2/−0
- src/Data/Hermes/Decoder/Value.hs +63/−0
- src/Data/Hermes/SIMDJSON/Bindings.hs +4/−0
- tests/test.hs +20/−1
CHANGELOG.md view
@@ -1,5 +1,16 @@ # Revision history for hermes-json +## 0.8.1.0 -- 2026-08-30++### Changed+* Update simdjson to 4.6.9++### Added+* Add `objectFold` for folding over object fields with a dependent state -+ thanks to @mpscholten!+* Add `withRawJsonByteString` for access to the raw JSON of the current value -+ thanks to @mpscholten!+ ## 0.8.0.0 -- 2026-03-25 Maintenance release
cbits/lib.cpp view
@@ -202,6 +202,20 @@ len = buf.length(); } + error_code get_raw_json(+ ondemand::value &val,+ const char **out,+ size_t &len) {+ std::string_view buf;+ auto error = val.raw_json().get(buf);+ if (error) {+ return error;+ }+ *out = buf.data();+ len = buf.length();+ return SUCCESS;+ }+ error_code is_null(ondemand::value &val, bool &out) { return val.is_null().get(out); }
hermes-json.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: hermes-json-version: 0.8.0.0+version: 0.8.1.0 category: Text, Web, JSON, FFI synopsis: Fast JSON decoding via simdjson C++ bindings description:
simdjson/singleheader/simdjson.cpp view
file too large to diff
simdjson/singleheader/simdjson.h view
file too large to diff
src/Data/Hermes.hs view
@@ -51,6 +51,7 @@ , vector , nullable , objectAsKeyValues+ , objectFold , objectAsMap , objectAsMapExcluding , liftObjectDecoder@@ -84,6 +85,7 @@ , withVector -- * Raw ByteString access , withRawByteString+ , withRawJsonByteString -- * simdjson Opaque Types , Array , ArrayIter
src/Data/Hermes/Decoder/Value.hs view
@@ -18,6 +18,7 @@ , nullable , object , objectAsKeyValues+ , objectFold , objectAsMap , objectAsMapExcluding , parseScientific@@ -33,6 +34,7 @@ , withInt , withObjectAsMap , withRawByteString+ , withRawJsonByteString , withRawText , withScientific , withString@@ -115,6 +117,16 @@ withRawByteString f = Decoder $ \val -> getRawByteString val >>= \b -> runDecoder (f b) val {-# INLINE withRawByteString #-} +-- | Helper to work with the complete raw JSON representation of the current+-- value. Unlike 'withRawByteString', this consumes and returns a complete+-- nested array or object rather than only its opening token.+withRawJsonByteString :: (BS.ByteString -> Decoder a) -> Decoder a+withRawJsonByteString f =+ Decoder $ \val ->+ getRawJsonByteString val >>= \bytes ->+ runDecoder (f bytes) val+{-# INLINE withRawJsonByteString #-}+ -- | Helper to work with the raw ByteString of the JSON token parsed from the given Value. withRawText :: (Text -> Decoder a) -> Decoder a withRawText f = Decoder $ \val -> getRawText val >>= \b -> runDecoder (f b) val@@ -208,6 +220,17 @@ objectAsKeyValues kf vf = withObjectIter $ iterateOverFields kf vf {-# INLINE objectAsKeyValues #-} +-- | Fold over an object once, selecting the value decoder from the current+-- key and accumulator. This supports dependent object codecs without+-- materialising a key/value list or rescanning the object.+objectFold+ :: state+ -> (Text -> state -> Decoder state)+ -> Decoder state+objectFold initial step =+ withObjectIter $ iterateOverFieldsFold initial step+{-# INLINE objectFold #-}+ -- | Parse an object into a strict `Map`. objectAsMap :: Ord k@@ -516,6 +539,32 @@ pure $ DList.toList acc {-# INLINE iterateOverFields #-} +iterateOverFieldsFold+ :: state+ -> (Text -> state -> Decoder state)+ -> ObjectIter+ -> DecoderM state+iterateOverFieldsFold initial step iterPtr =+ withRunInIO $ \run ->+ F.alloca $ \lenPtr ->+ F.alloca $ \keyPtr ->+ allocaValue $ \valPtr -> run $ go initial keyPtr lenPtr valPtr+ where+ go !acc keyPtr lenPtr valPtr = do+ isOver <- fmap F.toBool . liftIO $ objectIterIsDoneImpl iterPtr+ if not isOver+ then do+ err <- liftIO $ objectIterGetCurrentImpl iterPtr keyPtr lenPtr valPtr+ handleErrorCode "" err+ keyLength <- fmap fromIntegral . liftIO $ F.peek lenPtr+ keyCString <- liftIO $ F.peek keyPtr+ key <- parseTextFromCStrLen (keyCString, keyLength)+ next <- withKey key $ runDecoder (step key acc) valPtr+ liftIO $ objectIterMoveNextImpl iterPtr+ go next keyPtr lenPtr valPtr+ else pure acc+{-# INLINE iterateOverFieldsFold #-}+ withUnorderedField :: Value -> Decoder a -> Object -> Text -> DecoderM a withUnorderedField vPtr f objPtr key = withRunInIO $ \run ->@@ -618,6 +667,20 @@ str <- F.peek strPtr Unsafe.unsafePackCStringLen (str, len) {-# INLINE getRawByteString #-}++getRawJsonByteString :: Value -> DecoderM BS.ByteString+getRawJsonByteString valPtr = do+ (errorCode, cString, byteLength) <-+ liftIO $+ F.alloca $ \stringPtr ->+ F.alloca $ \lengthPtr -> do+ errorCode <- getRawJSONImpl valPtr stringPtr lengthPtr+ byteLength <- fmap fromIntegral $ F.peek lengthPtr+ cString <- F.peek stringPtr+ pure (errorCode, cString, byteLength)+ handleErrorCode "raw JSON" errorCode+ liftIO $ Unsafe.unsafePackCStringLen (cString, byteLength)+{-# INLINE getRawJsonByteString #-} getRawText :: Value -> DecoderM Text getRawText valPtr =
src/Data/Hermes/SIMDJSON/Bindings.hs view
@@ -23,6 +23,7 @@ , getObjectFromValueImpl , getObjectIterFromValueImpl , getRawJSONTokenImpl+ , getRawJSONImpl , getStringImpl , getTypeImpl , intArrayImpl@@ -145,6 +146,9 @@ foreign import ccall unsafe "get_raw_json_token" getRawJSONTokenImpl :: Value -> Ptr CString -> Ptr CSize -> IO ()++foreign import ccall unsafe "get_raw_json" getRawJSONImpl+ :: Value -> Ptr CString -> Ptr CSize -> IO CInt foreign import ccall unsafe "get_type" getTypeImpl :: Value -> Ptr CInt -> IO CInt
tests/test.hs view
@@ -37,7 +37,26 @@ properties = testGroup "Properties" [rtProp, rtPropOptional, rtErrors, rtRecursiveDataType] units :: TestTree-units = testGroup "Units" [altCases, objectFields]+units = testGroup "Units" [altCases, objectFields, dependentObjectFold, rawJsonAccess]++dependentObjectFold :: TestTree+dependentObjectFold = testCase "dependent object fold" $+ decodeEither+ (objectFold (0 :: Int) $ \key total ->+ case key of+ "add" -> (+ total) <$> int+ "enabled" -> (\value -> if value then total + 100 else total) <$> bool+ _ -> total <$ withRawJsonByteString (const (pure ())))+ "{\"add\":2,\"ignored\":{\"nested\":[1,2]},\"enabled\":true,\"add\":3}"+ @?= Right 105++rawJsonAccess :: TestTree+rawJsonAccess = testCase "complete raw JSON access" $+ decodeEither+ (object $ atKey "payload" $+ withRawJsonByteString (\bytes -> pure bytes))+ "{\"payload\":{\"nested\":[1,true,null]}}"+ @?= Right "{\"nested\":[1,true,null]}" rtRecursiveDataType :: TestTree rtRecursiveDataType = testProperty "Round Trip With Recursive Data Type" $