Reputation: 117
If I have:
type a = B | C
How do I write the static members ToJson and FromJson?
I know how to write it for a Record Type (which is shown in the examples at Chiron: JSON + Ducks + Monads ) but I can't find any examples for a DU.
EDIT
Following s952163 helpful answer (and follow up comment), I have adapted the code to try and work with a 'simple' DU of choice A | B (rather than A of string | B of ...). My code is now:
type SimpleDU =
| A
| B
static member ToJson (t : SimpleDU) =
match t with
| A -> Json.writeNone "a"
| B -> Json.writeNone "b"
static member FromJson (_ : SimpleDU) =
json {
let! duA = Json.tryRead "a"
match duA with
| Some s -> return s
| None -> return SimpleDU.B
}
This compiles but when I try it with the sample operation code:
let a = A
let b = B
let a2json = a |> Json.serialize
let (json2a:SimpleDU) = a2json |> Json.deserialize
let b2json = b |> Json.serialize
let (json2b:SimpleDU) = b2json |> Json.deserialize
json2a is incorrectly returning SimpleDU.B
Upvotes: 2
Views: 1114
Reputation: 8551
An implementation which serializes A
to Object (map [("SimpleDU", String "a")])
instead of Object (map [("a", Null null)])
is:
#I @"..\packages\Chiron.6.1.0\lib\net40"
#I @"..\packages\Aether.8.1.2\lib\net35"
#r "Chiron.dll"
#r "Aether.dll"
open Chiron
type SimpleDU =
| A
| B
static member ToJson x =
Json.write "SimpleDU" <|
match x with
| A -> "a"
| B -> "b"
static member FromJson(_ : SimpleDU) =
json {
let! du = Json.tryRead "SimpleDU"
match du with
| Some "a" -> return A
| Some "b" -> return B
| Some x -> return! Json.error <| sprintf "%s is not a SimpleDU case" x
| _ -> return! Json.error "Not a SimpleDU JSON"
}
// val serializedA : Json = Object (map [("SimpleDU", String "a")])
let serializedA = A |> Json.serialize
let serializedB = B |> Json.serialize
let (a : SimpleDU) = serializedA |> Json.deserialize
let (b : SimpleDU) = serializedB |> Json.deserialize
let aMatches = a = A
let bMatches = b = B
let serializedABBAA = [ A; B; B; A; A ] |> Json.serialize
let (abbaa : SimpleDU list) = serializedABBAA |> Json.deserialize
let abbaaMatches = abbaa = [ A; B; B; A; A ]
// allFine = true
let allFine = aMatches && bMatches && abbaaMatches
let defects =
Array [ Object <| Map.ofList [ ("SimpleDU", String "c") ]
Object <| Map.ofList [ ("Foo", String "bar") ] ]
// attempt = Choice2Of2 "Not a SimpleDU JSON"
let (attempt : Choice<SimpleDU list, string>) = defects |> Json.tryDeserialize
Instead of "a"
and "b"
you could use true
and false
which would get rid of the Some x
case, but I'd rather have readable cases in the JSON.
Upvotes: 5
Reputation: 36688
Seems to me that https://stackoverflow.com/a/36828630/2314532 might help you. That answer points to this F# snippet defining ToString
and FromString
functions for discriminated unions:
open Microsoft.FSharp.Reflection
let toString (x:'a) =
match FSharpValue.GetUnionFields(x, typeof<'a>) with
| case, _ -> case.Name
let fromString<'a> (s:string) =
match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
|[|case|] -> Some(FSharpValue.MakeUnion(case,[||]) :?> 'a)
|_ -> None
You would still need to get from the string (just "A" or "B") to the full DU object (e.g., reading the rest of the DU's data in s952163's SimpleDU
example), and as I haven't used Chiron yet, I can't help you much there. But that may give you a starting point.
Upvotes: 0
Reputation: 6324
You can add static members to DUs as well. In Chiron Taming Types in the last paragraph there is a link mentioning that some examples with DUs should be up soon. However assuming you can't wait and that you prefer Chiron over Json.NET or FsPickler here is an example. Probably there are some other ways but I'm not familiar with Chiron's operators so I decided to use a computation expression (pilfered from Chiron Computation Expressions). The idea is that you can pattern match. So probably you can pattern match over more complicated DUs as well. If you are familiar with Chiron I'm sure it can be made more idiomatic. You can see that Chiron itself is using DUs, and for example the Json object is map.
#I @"..\packages\Chiron.6.1.0\lib\net40"
#I @"..\packages\Aether.8.0.2\lib\net35"
#I @"..\packages\FParsec.1.0.1\lib\net40-client"
#r "Chiron.dll"
#r "Aether.dll"
#r "Fparsec.dll"
open Aether
open Chiron
open Chiron.Operators
open FParsec
type SimpleDU =
|A of string
|B of int * bool
static member ToJson (x: SimpleDU) =
match x with
| A s -> Json.write "A" s
| B (i, b) -> Json.write "B" (i, b)
static member FromJson (_ : SimpleDU) =
json {
let! duA = Json.tryRead "A"
match duA with
| Some s -> return A s
| None ->
let! x = Json.read "B"
return B x
}
And here's how it works:
let a = A "Jason"
let b = B (13,true)
let a2json = a |> Json.serialize //val Json = Object (map [("A", String "Jason")])
let (json2a:SimpleDU) = a2json |> Json.deserialize //val json2a : SimpleDU = A "Jason"
let b2json = b |> Json.serialize
let (json2b:SimpleDU) = b2json |> Json.deserialize
There are some examples in the source code as well that might be useful for you:Chiron
Upvotes: 1