使用 Json.NET 将 F# 判别联合序列化为字符串

问题描述 投票:0回答:3

我正在尝试在序列化时进行从 F# 的可辨别联合到字符串的单向转换,而不是默认的“Case”:[value]”。能够再次反序列化该值不是问题。也许可以使用 Json。网络?

// Fsharp 4.1.0
open Newtonsoft.Json // 10.0.3

type HowLame =
| PrettyLame
| SuperLame

type Lame = {
    howLame: HowLame;
}

[<EntryPoint>]
let main argv =
    let lame = { howLame = PrettyLame }
    let ser = JsonConvert.SerializeObject(lame)

    // {"soLame":{"Case":"PrettyLame"}} by default
    printfn "%s" ser

    // Desired
    assert (ser = """{"soLame":"PrettyLame"}""")
    0 // return an integer exit code
json serialization f# json.net
3个回答
9
投票

创建自定义 Json.NET JsonConverter 并使用它来装饰可区分联合(“枚举样式”)足以使其按照我想要的方式工作。其中很大一部分是从 C# 中的@Brian Rogers 答案音译而来的 https://stackoverflow.com/a/22355712/1924257

open System
open Newtonsoft.Json // 10.0.3
open Newtonsoft.Json.Converters

type ToStringJsonConverter () =
    inherit JsonConverter()
    override this.CanConvert objectType = true;

    override this.WriteJson (writer: JsonWriter, value: obj, serializer: JsonSerializer): unit = 
        writer.WriteValue(value.ToString())

    override this.CanRead = false

    override this.ReadJson (reader: JsonReader, objectType: Type, existingValue: obj, serializer: JsonSerializer) : obj =
        raise (new NotImplementedException());

[<JsonConverter(typeof<ToStringJsonConverter>)>]
type HowLame =
| PrettyLame
| SuperLame

type Lame = {
    howLame: HowLame
}

[<EntryPoint>]
let main argv =
    let lame = { howLame = PrettyLame }
    let ser = JsonConvert.SerializeObject(lame)

    // {"howLame":"PrettyLame"}
    printfn "%s" ser

    0 // return an integer exit code

7
投票

如果您愿意将 DU 设为枚举(通过指定显式值,这可能没问题,因为没有“有效负载”),您可以使用标准

StringEnumConverter
:

#r "../packages/Newtonsoft.Json/lib/net45/Newtonsoft.Json.dll"
open Newtonsoft.Json

type HowLame = PrettyLame=0 | SuperLame=1
type Lame = { howLame: HowLame; }

// in contrast to DUs, enums must be qualified, i.e. Enum.Value
let lame = { howLame = HowLame.PrettyLame }

let settings = JsonSerializerSettings()
settings.Converters.Add(Converters.StringEnumConverter())

let ser = JsonConvert.SerializeObject(lame, settings)
// val ser : string = "{"howLame":"PrettyLame"}"

0
投票

现在2024年,我正在使用:

https://github.com/fsprojects/FSharp.Json

它使用 System.Text.Json 并很好地支持 DU。我是这样配置的:

namespace Sample.Json

open System.Text.Json
open System.Text.Json.Serialization

module Json =

    let jsonOptions = JsonSerializerOptions()
    jsonOptions.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull

    JsonFSharpConverter(
        unionEncoding =
            (JsonUnionEncoding.InternalTag
             ||| JsonUnionEncoding.NamedFields
             ||| JsonUnionEncoding.UnwrapOption
             ||| JsonUnionEncoding.AllowUnorderedTag

            ),
        allowOverride = true,
        unionTagCaseInsensitive = false
    )
    |> jsonOptions.Converters.Add


    let asJson (obj: 'a) =
        JsonSerializer.Serialize(obj, jsonOptions)

    let asJsonDocument (obj: 'a) : JsonDocument =
        JsonSerializer.Serialize(obj, jsonOptions) |> JsonDocument.Parse

    let fromJson<'a> (json: string) =
        JsonSerializer.Deserialize<'a>(json, jsonOptions)

缺点是对于某些场景(例如 Azure Functions),您无法直接绑定到 F# DU。因此,我需要将对象转换为 JsonDocument,然后使用上面的实用程序函数来回转换。

在 Cosmos 上,这是单个案例 DU 的结果示例

type CommandState = 
        | Created of DateTimeOffset
        | Completed of DateTimeOffset
        | Failed of failureDate: DateTimeOffset * errorMessage: string * callStack: string option
 
 ...
 "state": {
        "Case": "Created",
        "Item": "2024-01-10T16:39:19.3992788-03:00"
    },
 ...

当字段有名称时,它也会使用 json 中的名称。对我来说,到目前为止效果很好。我希望这有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.