rubik
rubik

Reputation: 9104

Serializing values to JSON array with pipes

I'd like to serialize incoming values to JSON. Every value has a toJSON instance. The end result should be a list. The current code is the following:

import Pipes
import qualified Pipes.Prelude as P

-- assume a source of elements
main :: IO ()
main = runEffect $ source >-> P.map encode >-> P.stdoutLn

The problem is that in this way every line contains a valid JSON object, but I want the whole result to be parseable. I'd like that before the first object a [ character is outputted, then every element followed by a comma, and finally another ]. How can I do this with pipes?

Current output:

$ prog
{"key": "value"}
{"key": "value"}

Desired output:

$ prog
[{"key": "value"},
{"key": "value"}]

I found pipes-aeson but I don't understand how should I use the functions it provides.

EDIT: I modified ErikR's answer to get a Consumer, but it does not output the closing bracket:

jsonExporter :: Consumer (FilePath, AnalysisResult) IO ()
jsonExporter = do
    lift $ putStr "["
    P.map encode >-> insertCommas
    lift $ putStr "]"

I cannot understand why.

Upvotes: 0

Views: 413

Answers (1)

ErikR
ErikR

Reputation: 52039

This pipe segment:

for cat $ \x -> lift $ do { putStr ", "; putStrLn x }

will emit a comma before each element in the pipe.

To give the first element special treatment, we just unroll the loop once:

insertCommas = do
  x1 <- await
  lift $ putStrLn x1      -- print first element w/o a comma
  for cat $ \x -> lift $ do { putStr ", "; putStrLn x }

Now you can write your streaming JSON pipeline as:

putStr "["
runEffect $ source >-> P.map encode >-> insertCommas
putStrLn "]"

Upvotes: 1

Related Questions