In some cases, a built-in Giraffe JSON serializer might be insufficient. In this short tip, we show how to override it.
I use my side-project KyivStationWalk as a set of my opinionated takes on software architecture. No wonder I use F# here. One of the types I use in this project to describe my domain is the subway station branch. There are 3 branches in Kyiv named by their traditional colors.
type Branch =
| Red
| Blue
| Green
By default Giraffe, the framework which I use as a web server, uses Newtonsoft.Json to serialize results to JSON. However, for discriminated union, it generates quite a lot of JSON so I’ve switched to System.Text.Json which is built into newer versions of .Net Core. In combination with FSharp.SystemTextJson package allows serializing discriminated unions more gracefully. All we need is to decorate Branch
type with JsonFSharpConverter(JsonUnionEncoding.BareFieldlessTags)
attribute.
Here’s an example of a serialized subway station. Pay attention to how neat looks branch
property.
{
"id": "5c2e1d0c867a6335386700d9",
"name": {
"en": "Svyatoshyn",
"ua": "Святошин"
},
"branch": "Red",
"location": {
"lattitude": 50.457903,
"longitude": 30.390614
}
}
But there is a downside. Since the default serializer is Newtonsoft.Json you have to bake in serialization into your request handlers. (please note that logging and error handling is omitted for brevity)
let getApproved =
fun next httpContext ->
task {
let! routes = DbAdapter.getApprovedRoutes |> Async.StartAsTask
let domainRoutes = DbMappers.dbRoutesToRoutes routes
let result = RouteModels.toShortRoutes domainRoutes
let serialized = JsonSerializer.Serialize(result, Common.serializerOptions)
return! text serialized next httpContext
}
Luckily enough Giraffe allows overriding serializer in order to use native json
instead of text
. In order to achieve this, you have to register the serializer of your choice in ASP.NET Core services registration section
let configureServices (services : IServiceCollection) =
services.AddGiraffe() |> ignore
services.AddSingleton<Json.ISerializer>(SystemTextJson.Serializer(Common.serializerOptions)) |> ignore
With this done we are now able to ditch serialization logic resulting in a cleaner handler
let getApproved =
fun next httpContext ->
task {
let! routes = DbAdapter.getApprovedRoutes |> Async.StartAsTask
let domainRoutes = DbMappers.dbRoutesToRoutes routes
let result = RouteModels.toShortRoutes domainRoutes
return! json result next httpContext
}
History
23rd December 2021 - published initial version