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