cassava
cassava copied to clipboard
Write to multiple records (multiline `toRecord`?)
I sometimes need to encode types that have multiple records in them, something like:
data Observation = Obvervation {
foo :: Integer
, bar :: Integer
}
data DayInTheField = DayInTheField {
day :: String,
, place :: String
, observations :: [Observation]
}
and, ideally, I'd like to encode each observation to its own record line. So:
DayInTheField{
day="Monday"
, place="Baltimore"
, observations=[Observation 1 2, Observation 3 4]
}
would encode to
day,place,foo,bar
Monday,Baltimore,1,2
Monday,Baltimore,3,4
Is that something that is possible with cassava? If not, is that a wishlist item that might be considered? I imagine it would either require a redefinition of Record or an intermediate type between Record and CSV (say, RecordChunk), so I can see that it might be intrusive.
In case anyone else comes across this, I implemented an ad-hoc solution by creating a new typeclass, and related encode functions:
class ToManyRecords x where
toRecords :: x -> [Record]
csvEncodeManyWith :: ToManyRecords a => EncodeOptions -> [a] -> ByteString
csvEncodeManyWith opts xs = encodeWith opts (concatMap toRecords xs)
csvEncodeMany :: ToManyRecords a => [a] -> ByteString
csvEncodeMany = csvEncodeManyWith defaultEncodeOptions
And writing instance for my types. Using the made-up type above, I'd have
instance ToRecord Observation where
toRecord obs = record [ toField $ foo obs
, toField $ bar obs
]
instance ToManyRecords DayInTheField where
toRecords ditf = map (record' <>) (map toRecord $ observations ditf)
where record' = record [ toField $ day ditf
, toField $ place ditf
]
I can then run csvEncodeMany
on some [DayInTheField]
to get what I need.
I'm leaving this open, though, unless you think I should close it, because it does seem like a feature which, in a more thought-out way, might be useful to some users.