This library provides F# union and record support for System.Text.Json.
The NuGet package is FSharp.SystemTextJson. However the namespace is System.Text.Json.Serialization like the base library.
There are two ways to use FSharp.SystemTextJson: apply it to all F# types by passing JsonSerializerOptions, or apply it to specific types with an attribute.
Add JsonFSharpConverter to the converters in JsonSerializerOptions, and the format will be applied to all F# types.
open System.Text.Json
open System.Text.Json.Serialization
let options = JsonSerializerOptions()
options.Converters.Add(JsonFSharpConverter())
JsonSerializer.Serialize({| x = "Hello"; y = "world!" |}, options)
// --> {"x":"Hello","y":"world!"}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!"}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 as
optionandResult. - 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 is particularly useful if you are passing your own data to a library that calls these functions itself and doesn't take options.
ASP.NET Core can be easily configured to use FSharp.SystemTextJson.
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 ->
options.JsonSerializerOptions.Converters.Add(JsonFSharpConverter()))
|> ignoreAnd you can then just do:
type MyTestController() =
inherit Controller()
member this.AddOne([<FromBody>] msg: {| value: int |}) =
{| value = msg.value + 1 |}To use F# types in SignalR hubs, add the following to your startup ConfigureServices:
member this.ConfigureServices(services: IServiceCollection) =
services.AddSignalR()
.AddJsonProtocol(fun options ->
options.PayloadSerializerOptions.Converters.Add(JsonFSharpConverter()))
|> ignoreAnd you can then just do:
type MyHub() =
inherit Hub()
member this.AddOne(msg: {| value: int |})
this.Clients.All.SendAsync("AddedOne", {| value = msg.value + 1 |})Records and anonymous records are serialized as JSON objects.
type Example = { x: string; y: string }
JsonSerializer.Serialize { x = "Hello"; y = "world!" }
// --> {"x":"Hello","y":"world!"}
JsonSerializer.Serialize {| x = "Hello"; y = "world!" |}
// --> {"x":"Hello","y":"world!"}Named record fields are serialized in the order in which they were declared in the type declaration.
Anonymous record fields are serialized in alphabetical order.
Unions are serialized with the same format as Newtonsoft.Json, that is, a JSON object with two fields:
"Case": a string whose value is the name of the union case."Fields": an array whose items are the arguments of the union case. This field is absent if the union case has no arguments.
type Example =
| WithArgs of int * string
| NoArgs
JsonSerializer.Serialize NoArgs
// --> {"Case":"NoArgs"}
JsonSerializer.Serialize (WithArgs (123, "Hello world!"))
// --> {"Case":"WithArgs","Fields":[123,"Hello world!"]}Union cases that are represented as null in .NET using CompilationRepresentationFlags.UseNullAsTrueValue, such as Option.None, are serialized as null.
- Does FSharp.SystemTextJson support struct records and unions?
Yes!
- Does FSharp.SystemTextJson support anonymous records?
Yes!
- Does FSharp.SystemTextJson support alternative formats for unions? I find
"Case"/"Fields"ugly.
Not yet. (issue)
- Does FSharp.SystemTextJson support
JsonPropertyNameAttributeandJsonIgnoreAttributeon record fields?
Yes!
- Does FSharp.SystemTextJson support options such as
PropertyNamingPolicyandIgnoreNullValues?
Not yet. (issue, issue, issue)
- Does FSharp.SystemTextJson allocate memory?
As little as possible, but unfortunately the FSharp.Reflection API it uses requires some allocations. In particular, an array is allocated for as many items as the record fields or union arguments, and structs are boxed.
- Are there any benchmarks, eg. against Newtonsoft.Json?
Not yet. (issue)