Search code examples
serializationf#discriminated-union

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


Solution

  • 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.