packages feed

jsonl-0.1.0.0: src/JSONL.hs

{-# 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