Десериализация в перечисление опции в F # - PullRequest
0 голосов
/ 11 ноября 2019

Пару дней назад я опубликовал вопрос о десериализации с перечислениями в F #. Вопрос здесь: Десериализация в F # против C #

Ответ указывает на некоторый код, написанный Исааком Абрахамом, по адресу: https://gist.github.com/isaacabraham/ba679f285bfd15d2f53e

Однако я сталкиваюсь с другимпроблема:

Если объект для десериализации имеет объект типа 'enum option', десериализация не удастся, тогда как он будет работать, если тип просто 'enum'.

Aминимальный пример:

type TestType =
    | A = 0
    | B = 1

type TestObjectA =
      {
           test : TestType
      }

type TestObjectB =
     {
         test : TestType option
     }


let x = "{\"test\":\"A\"}"
let TestA = Deserialize<TestObjectA> x // will work
let TestB = Deserialize<TestObjectB> x // will fail

и большой код десериализации по адресу: https://pastebin.com/95JZLa6j

Я поместил весь код в скрипку: https://dotnetfiddle.net/0Vc0Rh, но это не может бытьзапускать оттуда, поскольку поддерживаемая ими версия F # не примет ключевое слово 'object'.

Итак, мой вопрос: почему я не могу использовать тип опции для перечисления, но он работает на других типах? В качестве примечания, поскольку я довольно новичок в F #, я не совсем понимаю код Исаака, хотя я потратил некоторое время на его изучение и поиск и устранение неисправностей.

Насколько я понимаю, эта строка:|> Seq.map (fun (value, propertyInfo) -> Convert.ChangeType (value, propertyInfo.PropertyType))

попытается преобразовать тип в перечисление справа, но не в параметр перечисления.

В качестве дополнительного вопроса, есть ли рабочее решение, которое выполняет полную идиоматическую десериализацию с помощью перечислений? (без прохождения нулевых типов)

Ответы [ 2 ]

0 голосов
/ 13 ноября 2019

В качестве дополнительного вопроса, есть ли рабочее решение, которое выполняет полную идиоматическую десериализацию с помощью перечислений?

Я использую пакет Microsoft.FsharpLu.Json в производствеи обнаружим, что он довольно хорошо работает для сериализации и десериализации между «простым» javascript и идиоматическим F #. Примечание Microsoft.FsharpLu.Json опирается на Newtonsoft.Json под капотом.

Ниже приведен пример с вашими типами и вашей тестовой строкой, использующий Expecto для тестов.

namespace FsharpLuJsonTest

open Newtonsoft.Json
open Microsoft.FSharpLu.Json
open Expecto
open Expecto.Flip

// Setup for FSharpLu.Json
type JsonSettings =
    static member settings =
        let s = JsonSerializerSettings(
                    NullValueHandling = NullValueHandling.Ignore,
                    MissingMemberHandling = MissingMemberHandling.Ignore)
        s.Converters.Add(CompactUnionJsonConverter())
        s
    static member formatting = Formatting.None

type JsonSerializer = With<JsonSettings>

// Your example
type TestType =
    | A = 0
    | B = 1

type TestObjectA = { test : TestType }
type TestObjectB = { test : TestType option }

module Tests =

    let x = """{"test":"A"}"""

    [<Tests>]
    let tests =
        testList "Deserialization Tests" [
            testCase "To TestObjectA" <| fun _ ->
                JsonSerializer.deserialize x
                |> Expect.equal "" { TestObjectA.test = TestType.A }

            testCase "To TestObjectB" <| fun _ ->
                JsonSerializer.deserialize x
                |> Expect.equal "" { TestObjectB.test = Some TestType.A }
        ]

module Main =

    [<EntryPoint>]
    let main args =
        runTestsInAssembly defaultConfig args

Как вы можете видеть FsharpLu.Json поддерживает дискриминированные союзы и типы опций "из коробки" так, как вы предпочитаете. FsharpLu.Json является менее гибким решением, чем некоторые другие, такие как Chiron (которые допускают гораздо больше настроек), но я склонен предпочесть взвешенный подход FsharpLu.Json.


IЯ не использовал его лично, но новая библиотека FSharp.SystemText.Json с настройкой JsonUnionEncoding.ExternalTag должна работать примерно так же, как FsharpLu.Json. Эта библиотека использует новую библиотеку Microsoft System.Text.Json под капотом, а не Newtonsoft.Json.

0 голосов
/ 11 ноября 2019
open System.IO

type TestType =
    | A = 0
    | B = 1

type TestObjectB =
     {
         test : TestType option
     }

let jsonSerializeToString obj = 
    use writer = new StringWriter()
    let ser =  new Newtonsoft.Json.JsonSerializer()
    ser.Formatting <- Newtonsoft.Json.Formatting.Indented
    ser.Serialize(writer, obj)
    writer.ToString()

let jsonDeserializeFromString str =
    Newtonsoft.Json.JsonConvert.DeserializeObject<TestObjectB>(str)

let Test obj = 
    let str = jsonSerializeToString obj
    let obj' = jsonDeserializeFromString str
    obj'

[<EntryPoint>]
let main argv =
    { test = Some TestType.B } |> Test |> ignore
    { test = None } |> Test |> ignore
    0

Примечание: если вам нужно сериализовать большую коллекцию объектов, то передайте их в файл вместо строки в памяти, чтобы избежать исключения OutOfMemoryException. Как use writer = File.CreateText(filePath).

...