Невозможно сериализовать Дискриминационный Союз в F# Chiron

Если у меня есть:

type a = B | C

Как мне написать статические члены ToJson и FromJson?

Я знаю, как написать его для типа записи (который показан в примерах на Chiron: JSON + Ducks + Monads), но я не могу найти никаких примеров для DU.


РЕДАКТИРОВАТЬ

После s952163 полезного ответа (и последующего комментария) я адаптировал код, чтобы попытаться работать с "простым" DU выбора A | B (а не A из строки | B из...). Мой код сейчас:

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

3 ответа

Решение

Реализация, которая сериализует 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" вы могли бы использовать trueа также false который бы избавился от Some x случае, но я бы предпочел читаемые случаи в JSON.

Вы также можете добавить статические члены в DU. В Chiron Taming Types в последнем параграфе есть ссылка, в которой говорится, что некоторые примеры с DU должны скоро появиться. Однако при условии, что вы не можете ждать, и что вы предпочитаете Chiron, а не Json.NET или FsPickler, вот пример. Возможно, есть и другие способы, но я не знаком с операторами Chiron, поэтому я решил использовать вычислительное выражение (взято из 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

Мне кажется, что /questions/5433557/sozdat-diskriminirovannoe-obedinennoe-delo-iz-stroki/5433565#5433565 может вам помочь. Этот ответ указывает на этот фрагмент кода F#, определяющий ToString а также FromString функции для дискриминируемых союзов:

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 (например, прочитать остальные данные DU в s952163). SimpleDU пример), и поскольку я еще не использовал Chiron, я не могу вам чем-то помочь. Но это может дать вам отправную точку.

Другие вопросы по тегам