How to use FSharp.SystemTextJson
January 20, 2025 ยท View on GitHub
- Installing
- Using directly
- Using with ASP.NET Core MVC
- Using with Giraffe
- Using with SignalR
- Using with Bolero
Installing
To use FSharp.SystemTextJson, install the NuGet package in your project.
The namespace to open is System.Text.Json.Serialization.
Using directly
There are two ways to use FSharp.SystemTextJson.
The recommended way is to apply it to all F# types by passing JsonSerializerOptions.
You can also apply it to specific types with an attribute.
Using options
Create a JsonSerializerOptions from JsonFSharpOptions, or add your JsonFSharpOptions to an existing JsonSerializerOptions,
and the format will be applied to all F# types.
open System.Text.Json
open System.Text.Json.Serialization
// 1. Either create the serializer options from the F# options...
let options =
JsonFSharpOptions.Default()
// Add any .WithXXX() calls here to customize the format
.ToJsonSerializerOptions()
// 2. ... Or add the F# options to existing serializer options.
JsonFSharpOptions.Default()
// Add any .WithXXX() calls here to customize the format
.AddToJsonSerializerOptions(options)
// 3. Either way, pass the options to Serialize/Deserialize.
JsonSerializer.Serialize({| x = "Hello"; y = "world!" |}, options)
// --> {"x":"Hello","y":"world!"}
Using attributes
Add JsonFSharpConverterAttribute to the type that needs to be serialized.
open System.Text.Json
open System.Text.Json.Serialization
[<JsonFSharpConverter>]
type Example = { x: string; y: string }
JsonSerializer.Serialize({ x = "Hello"; y = "world!" })
// --> {"x":"Hello","y":"world!"}
Advantages and inconvenients
The options way is generally recommended because it applies the format to all F# types. In addition to your defined types, this also includes:
- Types defined in referenced libraries that you can't modify to add an attribute.
This includes standard library types such asoptionandResult,list,MapandSet. - Anonymous records.
The attribute way cannot handle the above cases.
The advantage of the attribute way is that it allows calling Serialize and Deserialize without having to pass options every time.
This may be useful if you are passing your own data to a library that calls these functions itself and doesn't take options.
Using with ASP.NET Core MVC
To use F# types in MVC controllers, add the following to your startup ConfigureServices:
member this.ConfigureServices(services: IServiceCollection) =
services.AddControllersWithViews() // or whichever method you're using to get an IMvcBuilder
.AddJsonOptions(fun options ->
JsonFSharpOptions.Default()
.AddToJsonSerializerOptions(options.JsonSerializerOptions))
|> ignore
And you can then just do:
type MyTestController() =
inherit Controller()
member this.AddOne([<FromBody>] msg: {| value: int |}) =
{| value = msg.value + 1 |}
Using with Giraffe
To use FSharp.SystemTextJson in Giraffe (for example with the json function):
-
With Giraffe 7.x,
FSharp.SystemTextJsonis supported out of the box using theFSharpFriendlySerializer:open System.Text.Json open System.Text.Json.Serialization open Giraffe let configureServices (services: IServiceCollection) = services.AddGiraffe() |> ignore let jsonOptions = JsonFSharpOptions.Default() // .Add customizations here... services.AddSingleton<Json.ISerializer>(Json.FsharpFriendlySerializer(jsonOptions)) |> ignore -
With Giraffe 5.x or 6.x, add the following to your
configureServicesfunction:open System.Text.Json open System.Text.Json.Serialization open Giraffe let configureServices (services: IServiceCollection) = services.AddGiraffe() |> ignore let jsonOptions = JsonFSharpOptions.Default() // .Add customizations here... .ToJsonSerializerOptions() services.AddSingleton<Json.ISerializer>(SystemTextJson.Serializer(jsonOptions)) |> ignore // ... -
Giraffe 4.x or earlier doesn't have the above
SystemTextJsonSerializer, so you need to implement it in your project:open System open System.Text.Json open Giraffe.Serialization type SystemTextJsonSerializer(options: JsonSerializerOptions) = interface IJsonSerializer with member _.Deserialize<'T>(string: string) = JsonSerializer.Deserialize<'T>(string, options) member _.Deserialize<'T>(bytes: byte[]) = JsonSerializer.Deserialize<'T>(ReadOnlySpan bytes, options) member _.DeserializeAsync<'T>(stream) = JsonSerializer.DeserializeAsync<'T>(stream, options).AsTask() member _.SerializeToBytes<'T>(value: 'T) = JsonSerializer.SerializeToUtf8Bytes<'T>(value, options) member _.SerializeToStreamAsync<'T>(value: 'T) stream = JsonSerializer.SerializeAsync<'T>(stream, value, options) member _.SerializeToString<'T>(value: 'T) = JsonSerializer.Serialize<'T>(value, options)and then add the same code as for Giraffe 5.x in your
configureServicesfunction.
Using with SignalR
To use F# types in SignalR hubs, add the following to your startup ConfigureServices:
member this.ConfigureServices(services: IServiceCollection) =
services.AddSignalR()
.AddJsonProtocol(fun options ->
JsonFSharpOptions.Default()
.AddToJsonSerializerOptions(options.PayloadSerializerOptions))
|> ignore
And you can then just do:
type MyHub() =
inherit Hub()
member this.AddOne(msg: {| value: int |})
this.Clients.All.SendAsync("AddedOne", {| value = msg.value + 1 |})
Using with Bolero
Since version 0.14, Bolero uses System.Text.Json and FSharp.SystemTextJson for its Remoting.
To use FSharp.SystemTextJson with its default options, there is nothing to do.
To customize FSharp.SystemTextJson (see Customizing), pass a function to the AddRemoting method in both the client-side and server-side setup.
You can use the same function to ensure that both use the exact same options:
// src/MyApp.Client/Startup.fs:
module Program =
// Customize here
let serializerOptions (options: JsonSerializerOptions) =
JsonFSharpOptions.Default()
.AddToJsonSerializerOptions(options)
[<EntryPoint>]
let Main args =
let builder = WebAssemblyHostBuilder.CreateDefault(args)
builder.RootComponents.Add<Main.MyApp>("#main")
builder.Services.AddRemoting(builder.HostEnvironment, serializerOptions) |> ignore
builder.Build().RunAsync() |> ignore
0
// src/MyApp.Server/Startup.fs:
member this.ConfigureServices(services: IServiceCollection) =
services.AddRemoting<MyRemoteService>(MyApp.Client.Program.serializerOptions) |> ignore
// ...