无法在 F# Chiron 中序列化受歧视的联盟

Unable to serialize Discriminated Union in F# Chiron

如果我有:

type a = B | C

如何编写静态成员 ToJson 和 FromJson?

我知道如何为记录类型编写它(在 Chiron: JSON + Ducks + Monads 的示例中显示)但我找不到 DU 的任何示例。


编辑

根据 s952163 有用的答案(以及后续评论),我调整了代码以尝试使用 'simple' DU 选择 A | B(而不是字符串 A | B of ...)。我的代码现在是:

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
        }

这可以编译,但是当我用示例操作代码尝试时:

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 返回错误 SimpleDU.B

您也可以向 DU 添加静态成员。在 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)。这个想法是你可以模式匹配。因此,您可能也可以对更复杂的 DU 进行模式匹配。如果您熟悉 Chiron,我相信它可以变得更加地道。可以看到Chiron本身就是在使用DU,比如Json这个对象就是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
      }

这是它的工作原理:

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 

源代码中还有一些示例可能对您有用:Chiron

在我看来, might help you. That answer points to this F# snippet 定义了 ToStringFromString 函数来区分联合:

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

您仍然需要从字符串(仅 "A" 或 "B")获取完整的 DU 对象(例如,读取 s952163 的 SimpleDU 中 DU 的其余数据) example),因为我还没有使用过 Chiron,所以我帮不了你太多。但这可能会给你一个起点。

A 序列化为 Object (map [("SimpleDU", String "a")]) 而不是 Object (map [("a", Null null)]) 的实现是:

#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

而不是 "a""b" 你可以使用 truefalse 这将摆脱 Some x 的情况,但我宁愿在 JSON.

中有可读的案例