【问题标题】:deserialization issue, with json.net, in F#F# 中的 json.net 反序列化问题
【发布时间】:2020-10-03 05:55:13
【问题描述】:

我有一个要反序列化的简单对象,但我不明白我得到的错误。

代码如下:

open System
open Newtonsoft.Json

type r =
    {
        Timestamp:           DateTime
        Currency:            string

        PreviousDeposited:   int64 option
        PreviousWithdrawn:   int64 option
        PreviousTransferIn:  int64 option
        PreviousTransferOut: int64 option
        PreviousAmount:      int64 option

        TransferIn:          int64 option
        TransferOut:         int64 option

        Amount:              int64 option

        PendingCredit:       int64 option
        PendingDebit:        int64 option
        ConfirmedDebit:      int64 option
    }


let a =
    "{
    \"account\": 117122,
    \"currency\": \"XBt\",
    \"prevDeposited\": 747841316,
    \"prevWithdrawn\": 2160000,
    \"prevTransferIn\": 1000000,
    \"prevTransferOut\": 0,
    \"prevAmount\": 656893723,
    \"prevTimestamp\": \"2020-06-13T12:00:00.005Z\",
    \"deltaDeposited\": 0,
    \"deltaWithdrawn\": 0,
    \"deltaTransferIn\": 0,
    \"deltaTransferOut\": 0,
    \"deltaAmount\": 0,
    \"deposited\": 747841316,
    \"withdrawn\": 2160000,
    \"transferIn\": 1000000,
    \"transferOut\": 0,
    \"amount\": 656893723,
    \"pendingCredit\": 0,
    \"pendingDebit\": 0,
    \"confirmedDebit\": 0,
    \"timestamp\": \"2020-06-13T12:00:00.643Z\",
    \"addr\": \"2NBMEXRW4oCiNzVUq4uVFRSsK2jtTLbtfc7\",
    \"script\": \"532102c10be2f0dc20f4285c25156aa22a0c46d2b89ccc4d1c8eaed92ea0c1a8f40c002102ceba29da1af96a0f2ef7cda6950b8be2baeb1adf12c0d5efebb70dbcaa086ba021034ab762f4ede40311e9f8bf01db0bbea578497ac6ccc8aa94a74394b05a53d94b2103d5a42b90e9d7156155661979530a09d2e12e252ef4104e5611274a7ae7e2b09454ae\",
    \"withdrawalLock\": []
    }"


JsonConvert.DeserializeObject<r> a

我得到这个错误:

Newtonsoft.Json.JsonSerializationException:意外的属性 读取联合时发现“transferOut”。路径“transferOut”,第 18 行, 位置 18.] 在 Newtonsoft.Json.Converters.DiscriminatedUnionConverter.ReadJson(JsonReader reader, 类型 objectType, Object existingValue, JsonSerializer 序列化器)在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter 转换器、JsonReader 阅读器、类型 objectType、Object existingValue)
在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(JsonObjectContract 合约、JsonProperty containerProperty、JsonReader reader、Type 对象类型)在 ...

我不明白是什么让“TransferOut”属性如此特别,以至于它停在这个属性上,而不是之前的任何其他相同属性上。

我这里有个小提琴:https://dotnetfiddle.net/HGiia5

【问题讨论】:

  • 您可能需要为此考虑FsPickler。有利于沟通,但不利于持久。
  • @BentTranberg,它看起来像一个有趣的库。但在这种情况下它无法工作,因为我从第 3 方获取数据,对其进行处理并将其发送到具有多个消费者的队列。

标签: f# json.net


【解决方案1】:

这里有一些问题。

首先,您用于 option 字段的 JSON 语法与 Json.NET 的语法不匹配。如果我们将您的类型简化如下:

type r =
    {
        TransferIn:          int64 option
        TransferOut:         int64 option
    }

并按如下方式序列化一个实例:

let item : r = { TransferIn = Some 1000000L; TransferOut = None}
let json = JsonConvert.SerializeObject(item,Formatting.Indented)
printfn "%s" json
let item2 = JsonConvert.DeserializeObject<r> json // No exception

结果是:

{
  "TransferIn": {
    "Case": "Some",
    "Fields": [
      1000000
    ]
  },
  "TransferOut": null
}

哪些往返成功。演示小提琴 #1 here.

您用于option 字段的简单语法"transferIn": 1000000 未由DiscriminatedUnionConverter 实现,该转换器是Json.NET 用于序列化可区分联合(包括可选字段)的转换器。这种不匹配导致读取 JSON 时出现异常。

与此相关,请参阅 Serializing F# Option types,其中建议使用 nuget 包,该包为支持这种简化语法的option&lt;_&gt; 提供JsonConverter

其次,许多 JSON 属性名称与您的 f# 记录名称不匹配。 Json.NET 使用不区分大小写的算法将 JSON 属性名称与 f# 构造函数参数和成员名称匹配,但您的许多 JSON 名称不匹配:

  • "prevDeposited"PreviousDeposited 不匹配。
  • "prevWithdrawn"PreviousWithdrawn 不匹配。
  • 还有其他几个。

事实上,JSON 中第一个真正匹配option 字段的属性是"transferIn"。您收到关于 "transferOut" 的错误,因为它紧跟在未成功反序列化的 "transferIn" 的值之后。

最后,当字段没有出现在 JSON 对象的末尾时,Json.NET 针对 option 字段的无效 JSON 值抛出的错误消息是无用的 。如果我将输入 JSON 简化如下:

{
    "transferIn": 1000000,
}

我们得到一个更有用的错误信息

Newtonsoft.Json.JsonSerializationException:找不到具有联合名称的“Case”属性。路径'',第 3 行,位置 1。

演示小提琴 #2 here

但是,当"transferIn" 后跟另一个 JSON 键/值对时,错误消息将成为您的问题中显示的不太有用的消息。当option 字段的 JSON 值与预期模式不匹配且包含对象中存在后续 JSON 属性时,您可能会向 Newtonsoft 打开一个issue,要求他们改进DiscriminatedUnionConverter 抛出的错误消息。

【讨论】:

  • 感谢您深入了解它。关于第 2 点,这是一个疏忽,我转换了我关心的名称,但尚未检查其他名称。我同意这个错误不是很清楚,我会打开一个问题并参考这个线程。我从服务器获取 json。有时属性存在,有时则不存在。由于 DU 转换器不处理选项类型,有没有更好的方法来处理这种情况?
  • 您需要为使用简化语法的option&lt;_&gt; 创建一个自定义JsonConverter。困难在于在[AllowNullLiteral] 类型的序列化过程中区分Some nullNone。但如果你不关心这一点,我只是通过谷歌搜索找到了Serializing F# Option types(使用 MIT 许可证)和来自github.com/haf/Newtonsoft.Json.FSharpOptionConverter——但我找不到它的许可证。
  • 我刚刚找到了 Microsoft.FSharpLu.Json,我也会调查一下。
  • 几乎就在那里:Microsoft.FSharpLu.Json 完美运行(它建立在 Json.net 之上并添加了自己的 DU 解析器等),但现在还有一个额外的问题,即如果有它会失败json 中的某些成员在类型中不存在。设置 CheckAdditionalContent
  • 看起来 Microsoft.FSharpLu.Json 在 dotnetfiddle 上可用,请参阅 dotnetfiddle.net/OLjF1S,所以我建议用 minimal reproducible example 显示您当前的代码和问题提出另一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-02
  • 1970-01-01
  • 2012-01-20
  • 2012-04-09
相关资源
最近更新 更多