Overriding JSON Serializer In Giraffe

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
    }