2011-09-01 19:16:31 +00:00
|
|
|
{- Streaming JSON output.
|
|
|
|
-
|
2016-07-26 17:30:07 +00:00
|
|
|
- Copyright 2011, 2016 Joey Hess <id@joeyh.name>
|
2011-09-01 19:16:31 +00:00
|
|
|
-
|
2014-05-10 14:01:27 +00:00
|
|
|
- License: BSD-2-clause
|
2011-09-01 19:16:31 +00:00
|
|
|
-}
|
|
|
|
|
2016-07-26 17:30:07 +00:00
|
|
|
{-# LANGUAGE GADTs #-}
|
|
|
|
|
2011-09-01 19:16:31 +00:00
|
|
|
module Utility.JSONStream (
|
2016-07-26 17:30:07 +00:00
|
|
|
JSONChunk(..),
|
2011-09-01 19:16:31 +00:00
|
|
|
start,
|
|
|
|
add,
|
|
|
|
end
|
|
|
|
) where
|
|
|
|
|
2016-07-26 23:15:34 +00:00
|
|
|
import Data.Aeson
|
|
|
|
import qualified Data.Text as T
|
2016-07-27 01:43:05 +00:00
|
|
|
import qualified Data.ByteString.Lazy as B
|
|
|
|
import Data.Char
|
|
|
|
import Data.Word
|
2011-09-01 19:16:31 +00:00
|
|
|
|
2016-07-26 23:15:34 +00:00
|
|
|
data JSONChunk v where
|
|
|
|
JSONChunk :: ToJSON v => [(String, v)] -> JSONChunk [(String, v)]
|
|
|
|
AesonObject :: Object -> JSONChunk Object
|
|
|
|
|
|
|
|
encodeJSONChunk :: JSONChunk v -> B.ByteString
|
|
|
|
encodeJSONChunk (JSONChunk l) = encode $ object $ map mkPair l
|
|
|
|
where
|
|
|
|
mkPair (s, v) = (T.pack s, toJSON v)
|
|
|
|
encodeJSONChunk (AesonObject o) = encode o
|
|
|
|
|
|
|
|
{- Aeson does not support building up a larger JSON object piece by piece
|
|
|
|
- with streaming output. To support streaming, a hack:
|
|
|
|
- The final "}" is left off the JSON, allowing more chunks to be added
|
|
|
|
- to later. -}
|
2016-07-27 01:43:05 +00:00
|
|
|
start :: JSONChunk a -> B.ByteString
|
2016-07-26 17:30:07 +00:00
|
|
|
start a
|
2016-07-27 01:43:05 +00:00
|
|
|
| not (B.null b) && B.last b == endchar = B.init b
|
|
|
|
| otherwise = bad b
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2016-07-27 01:43:05 +00:00
|
|
|
b = encodeJSONChunk a
|
2011-09-01 19:16:31 +00:00
|
|
|
|
2016-07-27 01:43:05 +00:00
|
|
|
add :: JSONChunk a -> B.ByteString
|
2016-07-26 17:30:07 +00:00
|
|
|
add a
|
2016-07-27 01:43:05 +00:00
|
|
|
| not (B.null b) && B.head b == startchar = B.cons addchar (B.drop 1 b)
|
|
|
|
| otherwise = bad b
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2016-07-27 01:43:05 +00:00
|
|
|
b = start a
|
2011-09-01 19:16:31 +00:00
|
|
|
|
2016-07-27 01:43:05 +00:00
|
|
|
end :: B.ByteString
|
|
|
|
end = endchar `B.cons` sepchar `B.cons` B.empty
|
2011-09-01 19:16:31 +00:00
|
|
|
|
2016-07-27 01:43:05 +00:00
|
|
|
startchar :: Word8
|
|
|
|
startchar = fromIntegral (ord '{')
|
2011-09-01 19:16:31 +00:00
|
|
|
|
2016-07-27 01:43:05 +00:00
|
|
|
endchar :: Word8
|
|
|
|
endchar = fromIntegral (ord '}')
|
2011-09-01 19:16:31 +00:00
|
|
|
|
2016-07-27 01:43:05 +00:00
|
|
|
addchar :: Word8
|
|
|
|
addchar = fromIntegral (ord ',')
|
|
|
|
|
|
|
|
sepchar :: Word8
|
|
|
|
sepchar = fromIntegral (ord '\n')
|
|
|
|
|
|
|
|
bad :: B.ByteString -> a
|
|
|
|
bad b = error $ "JSON encoder generated unexpected value: " ++ show b
|
2016-07-26 17:30:07 +00:00
|
|
|
|