{-# LANGUAGE RankNTypes #-} {-# language DeriveGeneric #-} {-# language OverloadedStrings #-} -- | JSON Lines https://jsonlines.org/ module JSONL ( -- * Encode jsonlWriteFile, jsonlToLBS, jsonlBuilder -- * Decode , jsonlFromLBS ) where import System.IO (Handle, IOMode(..), withBinaryFile) -- aeson import Data.Aeson (ToJSON(..), FromJSON(..), encode, eitherDecode' ) -- bytestring import qualified Data.ByteString.Builder as BBS (toLazyByteString, Builder, lazyByteString, string7) import qualified Data.ByteString.Builder.Internal as BBS (hPut, putBuilder) import qualified Data.ByteString.Internal as BS (c2w) import qualified Data.ByteString.Lazy as LBS (ByteString, span) import Prelude hiding (writeFile) -- | Parse a JSONL-encoded collection of objects from a `LBS.ByteString` -- -- If parsing fails, returns the first parsing error in a Left jsonlFromLBS :: FromJSON a => LBS.ByteString -> Either String [a] jsonlFromLBS = sequence . jsonlFromLBS_ jsonlFromLBS_ :: FromJSON a => LBS.ByteString -> [Either String a] jsonlFromLBS_ = go mempty where go :: FromJSON a => [Either String a] -> LBS.ByteString -> [Either String a] go acc lbs = let (s, srest) = LBS.span (== BS.c2w '\n') lbs in go (eitherDecode' s : acc) srest -- jsonlFromLBS' :: FromJSON a => LBS.ByteString -> Either String [a] -- jsonlFromLBS' = chop1 (Right mempty) -- chop1 :: FromJSON a => -- Either String [a] -> LBS.ByteString -> Either String [a] -- chop1 acce lbs = case acce of -- Right acc -> case eitherDecode' s of -- Right x -> chop1 (Right (x : acc)) srest -- Left e -> Left e -- ex -> ex -- where -- (s, srest) = LBS.span (== BS.c2w '\n') lbs -- | Write a collection of objects to a JSONL-encoded file jsonlWriteFile :: (Foldable t, ToJSON a) => FilePath -> t a -> IO () jsonlWriteFile fpath xs = writeFile fpath (jsonlBuilder xs) -- | Render a collection of objects to a JSONL-encoded `LBS.ByteString` jsonlToLBS :: (Foldable t, ToJSON a) => t a -> LBS.ByteString jsonlToLBS xs = BBS.toLazyByteString $ jsonlBuilder xs jsonlBuilder :: (Foldable t, ToJSON a) => t a -> BBS.Builder jsonlBuilder = foldMap jsonLine jsonLine :: ToJSON a => a -> BBS.Builder jsonLine x = jsonToBuilder x <> BBS.string7 "\n" jsonToBuilder :: ToJSON a => a -> BBS.Builder jsonToBuilder = BBS.lazyByteString . encode -- vendored from bytestring < 0.11.2 hPutBuilder :: Handle -> BBS.Builder -> IO () hPutBuilder h = BBS.hPut h . BBS.putBuilder modifyFile :: IOMode -> FilePath -> BBS.Builder -> IO () modifyFile mode f bld = withBinaryFile f mode (`hPutBuilder` bld) writeFile :: FilePath -> BBS.Builder -> IO () writeFile = modifyFile WriteMode