-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
User Story: Developers using JsonSerializer want it to handle common F# types well #29812
Comments
Should be:
|
This requires careful design to make sure it is handled correctly to meet consumer expectations. We should flesh out the requirements of what the behavior should be, especially for cases like round-tripping, etc. |
Suggest mining https://github.com/jet/Jet.JsonNet.Converters and https://github.com/microsoft/fsharplu for commonly shimmed features - will be monitoring this issue as I would love rebase the test codebase for the former on top of this and/or dogfood it here. Related issue: https://github.com/jet/equinox/issues/79 |
Would the discriminated unions support be helpful in non F# cases as well? I'm particularly interested in cases of deserializing protocols such as JSON-RPC, where the incoming message type may be any of a few schemas and we need to distinguish them based on a couple of properties. |
Good question @AArnott - supporting management of a catchall case would also ideally be possible. |
While this is being decided, I've implemented an F# serializer as a separate library: https://github.com/Tarmil/FSharp.SystemTextJson |
While I understand that the F# specific types like DUs and Records are not supported yet, I've run into strange behaviour serializing a simple Map. Given the following F# program (running in .Net Core 3 preview 9): open System
open System.Text.Json
[<EntryPoint>]
let main _argv =
let m1 = Map.empty<string, string> |> Map.add "answer" "42"
let json1 = JsonSerializer.Serialize(m1)
printfn "JSON1 %s" json1
let m2 = Map.empty<string, Version> |> Map.add "version" (Version("10.0.1.1"))
let json2 = JsonSerializer.Serialize(m2)
printfn "JSON2 %s" json2
0 I expect the following output:
But instead I get this:
So the first case worked, but not the second. Is this a known limitation or a bug? |
@hravnx, I think it is related to https://github.com/dotnet/corefx/issues/40949 |
|
Map<string, string[]> is also broken. Map<string, Uri> works. But I'll stop "polluting" now. Bye. |
Here is an example where the
|
^^ Runtime Environment: Host (useful for support): |
Just as a quick update, various things do work. This example now works in .NET 5: open System
open System.Text.Json
[<EntryPoint>]
let main _argv =
let m1 = Map.empty<string, string> |> Map.add "answer" "42"
let json1 = JsonSerializer.Serialize(m1)
printfn "JSON1 %s" json1
let m2 = Map.empty<string, Version> |> Map.add "version" (Version("10.0.1.1"))
let json2 = JsonSerializer.Serialize(m2)
printfn "JSON2 %s" json2
0 F# records and anonymous records now appear to work (nested records example): open System
open System.Text.Json
type Person = { Name: string; Age: int }
type Gaggle = { People: Person list; Name: string }
[<EntryPoint>]
let main _argv =
let r1 =
{
People =
[ { Name = "Phillip"; Age = Int32.MaxValue }
{ Name = "Ryan Nowak"; Age = Int32.MinValue } ]
Name = "Meme team"
}
let r2 =
{|
r1 with
FavoriteMusic = "Dan Roth's Banjo Bonanza"
|}
printfn $"{JsonSerializer.Serialize r1}"
printfn $"{JsonSerializer.Serialize r2}"
0 Yields: {"People":[{"Name":"Phillip","Age":2147483647},{"Name":"Ryan Nowak","Age":-2147483648}],"Name":"Meme team"}
{"FavoriteMusic":"Dan Roth\u0027s Banjo Bonanza","Name":"Meme team","People":[{"Name":"Phillip","Age":2147483647},{"Name":"Ryan Nowak","Age":-2147483648}]} But DUs are still missing. FSharp.SystemTextJson is still the way to serialize F# union types. |
@terrajobst @layomia happy to chat about what all would be the ideal format and what kind of data needs to be dealt with |
@cartermp, thanks - I'll reach out to discuss. |
Huh, I'm finding that I can't even deserialize lists of records, even with the CLIMutable attribute. This is with .NET 5.0.101. The following snippet...
throws...
|
@lambdakris For now, |
I would add the following types to the list:
Note that
|
Use https://github.com/Tarmil/FSharp.SystemTextJson/ as a reference. It covers almost all cases. Maybe not in the most perfect or easy-to-understand way... |
Concerning support for discriminated unions, it might be worth pointing out that there is no one canonical way to encode DUs in JSON. DUs are used in many different ways in F# code, including the following:
In light of the above, I'm increasingly starting to think that System.Text.Json should not be providing a default mechanism for serializing DUs. Users would still be able to use available custom converters that provide the union encoding that suits their use case or perhaps use unions in their data contracts in a way that bypasses the serialization layer altogether. |
@eiriktsarpalis we (and I think everybody else) use only option 2 (type-safe enums) at the edge ... and I think a performance-optimized (runtime reflection!) impl does 100% belong into System.Text.Json ... Currently we are forced to use standard enums ... we tried implementing simple DU serialization/deserialization incl. caching of the values ... but there was sth. suboptimal (need to remember) ... |
@deyanp as an aside, tagging the typesafe enum I would of course also like to see an in the box professional impl of it of course - nothing would give me more pleasure than removing it from FsCodec (although the various conditional parsing helpers from the As the FsCodec impl shows though, achieving a proper impl of that seemingly simple task is no entirely trivial even before you consider perf and has significant overlap with the work of doing a UnionConverter capable of handing stateful cases. Based on that I'd prefer to see any implementation work consider the broader set of use cases as Eirik has outlined; I definitely agree that any global default behavior is asking for trouble. |
Why not go for simple, but non-optimal translation, where a discriminated union is treated as a record where only one attribute is present, and where null is placed where no arguments are allowed for the constructor.
This would not be the most compact representation, but it would always work, as far as I can tell. |
@jhf see the linked issue for other proposals (and me attempting to explain why IMO you want to have a clean way to migrate all the way from nullary cases -> SCDU -> other arities of tuples -> records) In my proposal, the renderings would be: If using
The UnionConverter would enable:
(As mentioned in the other post, the That leaves what do to with SCDUs to force them to be represented as their body without I personally tend to use https://github.com/fsprojects/FSharp.UMX to side-step this (see also this very thorough blog article by @pblasucci) If a pushed, my suggestion for a way to handle this, would be to have an opt-in converter could cover this case - (While one could arguably offer that as a special case option on UnionConverter, I think that's fraught with problems - having a single canonical rendering per converter is pretty critical IMO) |
We should make sure that the JSON serializer can handle types / structures that commonly occur in F#, such as
/cc @steveharter @ahsonkhan
The text was updated successfully, but these errors were encountered: