Support F# data structures
Hi. Would be cool to have a support for typical F# data structures like these:
- Option
- List
- Discriminated unions
- One-Case discriminated unions
- Measure units
Now this feature is supported using Nuget package : Swifter.SharpExtensions.
Thank you! Well, it doesn't crash, which is already excellent. But there are still some bugs, making it useless:
open Swifter.Json
open Swifter.FSharpExtensions
type Payment = Cache of decimal | CreditCard of int | Free
[<Measure>] type cm
type Demo =
{ Id : int
Name : string
Age : int
BodyHeight : int<cm> option
Children : string list
Payment : Payment
WorkDone : Result<int,string> }
let value =
{ Id = 1
Name = "Dogwei"
Age = 42
BodyHeight = Some 195<cm>
Children = ["Andy"]
Payment = Free
WorkDone = Error "Not satisfied"
}
let json = JsonFormatter.SerializeObject value
let orig = JsonFormatter.DeserializeObject<Demo> json
Results in:
{"Age":42,"BodyHeight":{"Value":195},"Children":["Andy"],"Id":1,"Name":"Dogwei","Payment":{"IsCache":false,"IsCreditCard":false,"IsFree":true,"Tag":2},"WorkDone":{"ErrorValue":"Not satisfied","IsError":true,"IsOk":false,"ResultValue":0,"Tag":1}}
- json is needlessly ugly and long
- DUs are rendered with all the compiled properties, instead of particular case.
- at least
Optionis a special case and could be compiled as direct value in case ofSomeand asnullin case ofNone - Records can't be deserialized, because they have no parameterless constructor:
System.MissingMethodException: No parameterless constructor defined for type 'FSI_0024+Demo'.
at System.RuntimeType.CreateInstanceDefaultCtorSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean fillCache)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, Boolean wrapExceptions)
at System.Activator.CreateInstance(Type type)
at FastObjectRW_Demo_0ab5e71617344217b902ad0a43dc9848.Initialize()
at Swifter.Json.JsonDeserializer`1.ReadObject(IDataWriter`1 dataWriter)
at FastObjectRWCreater_Demo_8dd67e122a5d4111ba3c3d9e28d8fcda.ReadValue(IValueReader )
at <StartupCode$FSI_0025>.$FSI_0025.main@()
I'm sorry, can you tell me the right Json for this example?
I think honestly there is no "right", what fits for all. And it is not easy to implement it. I used to write json formatter, which would produce this:
{
"Age": 42,
"BodyHeight": 195,
"Children": [ "Andy" ],
"Id": 1,
"Name": "Dogwei",
"Payment": "Free",
"WorkDone": ["Error", "Not satisfied"]
}
However, there are multiple ways how to serialze DU (discriminated unions) in F#. Because they definitions can vary. Here for bson as an example:
- all cases without fields:
type BsonType = | BsonString | BsonInt32 | BsonDecimal- this case is better serialized as string
"BsonDecimal"
- this case is better serialized as string
- only some or all cases have a value:
type BsonValue = | BsonString of string | BsonInt32 of int32 | BsonNull | BsonMaxKey- there are multiple possibilities:
-
{"_t": "BsonString", "Item1": "my string" } -
{"Case": "BsonString", "Value": "my string" } -
["BsonString", "my string" ]or without value["BsonMaxKey"]- probably a compactest form fitting all variants at once -
{"BsonString" : "my string" }
-
- there are multiple possibilities:
- the value itself can be anything, including record, tuple, tuple with named fields, another DU value
- There are some special discriminated unions, which are used at most.
-
Option<TValue>should definitely unwrap contained value and serialized like this:None->null,Some "my string"->"my string" -
Result<TError,TSuccess>which can beOk "paiment success"->{"Ok" : "paiment success" }or["Ok", "paiment success"], and equal for "Error" case with another value type
-
The existing implementation is unfortunate for DUs with many cases.
.NET represents them as a class with Tag : int and per each case property CaseValue : TValue + IsCase1 : bool which is too much. So if BsonType has 20 cases, the class has 41 properties, where you need just one or two of them at a time - tag and if exists, corresponding value
It's OK now, but it's done with C#. I was to do it with F#, but something's hard for me; First, I don't know how to implement interfaces😂.

The extended implementation code is in the Swifter.FSharpExtensions project. I'll submit the code later.
Yes, F# OOP is crap. Glad you don't give up )))
Created a gist with port of current code without knowing what it does: https://gist.github.com/vilinski/def2f35f78f68c523f994aaa3d3f111d Let me know if I can help.