PeterB
PeterB

Reputation: 117

Unable to serialize Discriminated Union in F# Chiron

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

Answers (3)

CaringDev
CaringDev

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 trueand false which would get rid of the Some x case, but I'd rather have readable cases in the JSON.

Upvotes: 5

rmunn
rmunn

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

s952163
s952163

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

Related Questions