5. Content Serialization / Deserialization¶
In WebApi, ToParam
and FromParam
are the typeclasses responsible for serializing and deserializing data. Serialization and deserialization for your data types are automatically take care of if they have generic instances without you having to write anything. You still have to derive them though.
Lets look at an example
data LatLng = LatLng
{ lat :: Double
, lng :: Double
} deriving Generic
To let WebApi automatically deserialize this type, we just need to give an empty instance declaration
instance FromParam 'QueryParam LatLng
And to serialize a type (in case you are writing a client), you can give
a similar ToParam
instance.
instance ToParam 'QueryParam LatLng
5.1. Nested Types¶
If you use Generic
instance for nested types, they will be serialized with a dot notation.
data UserData = UserData
{ age :: Int
, address :: Text
, name :: Text
, location :: LatLng
} deriving (Show, Eq, Generic)
Here the location field would be serialized as
location.lat
and location.lng
5.2. Writing Custom instances¶
Sometimes you may want to serialize/deserialize the data to a custom format.
You can easily do this by writing a custom instance of ToParam
and
FromParam
. Lets declare a datatype and try to write ToParam
and
FromParam
instances for those.
data Location = Location { loc :: LatLng } deriving Generic
data LatLng = LatLng
{ lat :: Double
, lng :: Double
} deriving Generic
Lets say we want to deserialize query parameter loc=10,20
to
Location
where 10
and 20
are values of lat
and
lng
respectively. We can write a FromParam
instance for this as
follows:
instance FromParam 'QueryParam Location where
fromParam pt key trie = case lookupParam pt key trie of
Just (Just par) -> case splitOnComma par of
Just (lt, lg) -> case (LatLng <$> decodeParam lt <*> decodeParam lg) of
Just ll -> Validation $ Right (Location ll)
_ -> Validation $ Left [ParseErr key "Unable to cast to LatLng"]
Nothing -> Validation $ Left [ParseErr key "Unable to cast to LatLng"]
Just Nothing -> Validation $ Left [ParseErr key "Value not found"]
_ -> Validation $ Left [NotFound key]
where
splitOnComma :: ByteString -> Maybe (ByteString, ByteString)
splitOnComma x =
let (a, b) = C.break (== ',') x -- Data.ByteString.Char8 imported as C
in if (BS.null a) || (BS.null b) -- Data.ByteString imported as BS
then Nothing
else Just (a, b)
fromParam
takes a Proxy
of our type (here, Location
),
a key (ByteString
) and a Trie
.
WebApi
uses Trie
to store the parsed data while deserialization.
fromParam
returns a value of type Validation
which is a wrapper
over Either
type carrying the parsed result.
We use lookupParam
function for looking up the key (loc
).
If the key matches, it’ll return Just
with the value of the key (in our case 10,20
).
Now we split this value into a tuple using splitOnComma
and make a value
of type LatLng
using these.
Similarly, a ToParam
instance for Location
can be written as:
instance ToParam 'QueryParam Location where
toParam pt pfx (Location (LatLng lt lg)) = [("loc", Just $ encodeParam lt <> "," <> encodeParam lg)]
Here we take a value of type Location
and convert it into a key-value pair.
WebApi
uses this key-value pair to form the query string.
This example only included QueryParam
but this can be easily extended to
other param types.
5.3. Content Types¶
You can tell WebApi about the content-type of ApiOut/ApiErr
using
ContentTypes
.
instance ApiContract MyApiService POST User where
type FormParam POST User = UserData
type ApiOut POST User = UserToken
type ContentTypes POST User = '[JSON]
By default ContentTypes
is set to JSON
. That means you need to
give ToJSON
instances for the types associated with ApiOut/ApiErr
while writing server side component and FromJSON
instances while writing
client side version.
Apart from JSON
you can give other types such as HTML
, PlainText
etc. You can see a complete list here