Convert plain Haskell records to vinyl and vice versa, via GHC.Generics
and generics-sop
/records-sop
.
- Reading an external data source (database query, results of API requests etc.) as a list of plain Haskell records and converting it to a list of
vinyl
records (for subsequent conversion to an in-memory data-frame). - Serializing a
Frame
/list ofvinyl
records to JSON. - Adding/removing fields from a plain record using
vinyl
as an intermediate representation.
Consider the following example module:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Example where
import Data.Aeson
import Data.Text
import Data.Vinyl
import Data.Vinyl.Generics.Transform (fromVinyl, toVinyl)
import qualified Generics.SOP as S
import qualified GHC.Generics as G
data MyPlainRecord = MPR {
age :: Int,
iscool :: Bool,
yearbook :: Text
} deriving (Show, G.Generic)
instance S.Generic MyPlainRecord
instance S.HasDatatypeInfo MyPlainRecord
data MyType =
MyType {
bike :: Bool
, skateboard :: Bool
} deriving (Show, G.Generic)
data MyPlainRecord2 = MPR2 {
age :: Int,
iscool :: Bool,
yearbook :: Text,
hobbies :: MyType
} deriving (Show, G.Generic)
instance S.Generic MyPlainRecord2
instance S.HasDatatypeInfo MyPlainRecord2
In the above, let MyPlainRecord
be the format in which data is being read from an external source. We also read some additional data additionalFields
(say from a CSV using Frames
):
-- some mock data
r1 :: MyPlainRecord
r1 = MPR { age = 23, iscool = True, yearbook = "!123!"}
additionalFields :: Rec ElField '[("age" ::: Int), ("hobbies" ::: MyType)]
additionalFields = xrec (23, MyType { bike = True, skateboard = True})
We'd like to add the field hobbies :: MyType
to one such record ( :: MyPlainRecord
), and want to have a record of type MyPlainRecord2
. We can accomplish this by first isolating the field:
getHobbies :: Rec ElField '[("age" ::: Int), ("hobbies" ::: MyType)]
-> Rec ElField '[("hobbies" ::: MyType)]
getHobbies = rcast
...and then appending it to the vinyl
representation of the plain record:
go :: MyPlainRecord2
go = fromVinyl $ (toVinyl r1) `rappend` (getHobbies additionalFields)
That's all there is to it. Once we have our vinyl record in plain record form, it is straightforward to serialize it to JSON:
instance ToJSON MyPlainRecord2
instance ToJSON MyType
Have a look at test/LibSpec.hs
for more usage examples.
This library, in its current form, works only with vinyl
records with type-level field names (i.e. use the ElField
interpretation functor).
Future versions hope to tackle records with anonymous fields (e.g. heterogenous lists making use of the Identity
functor) as well.