.net - 使用 Json.NET 为 F# 记录标签填充复杂的默认值
问题描述
假设我有一个记录的第一个版本,我正在对其进行序列化和反序列化以保留它所代表的数据。现在我想为该记录添加一个新标签。由于 F# 记录是不可变的,因此在反序列化保留的旧版本记录时,应使用有意义的默认值填充该标签。
这适用于在该新标签上使用System.ComponentModel.DefaultValueAttribute的简单值。但不幸的是,我无法弄清楚如何在这里传递一个复杂的值。像另一张唱片,或歧视性工会之类的东西。
我会购买任何理智的解决方案。例如,我也很乐意注册一些在反序列化期间为给定类型提供默认值的函数。
open FsUnit
open NUnit.Framework
open Newtonsoft.Json
type Gender = | Female | Male | Other
type TenantV1 = { Name: string }
type TenantV3 =
{ Name: string;
[<System.ComponentModel.DefaultValue("John")>]
FirstName: string }
type TenantV4 =
{ Name: string;
[<System.ComponentModel.DefaultValue(typeof<Gender>, "Other")>]
Gender: Gender }
let serializationSettings = JsonSerializerSettings()
serializationSettings.TypeNameHandling <- TypeNameHandling.All
let deserializationSettings = JsonSerializerSettings()
deserializationSettings.DefaultValueHandling <- DefaultValueHandling.Populate
[<Test>] // OK
let ``Serialize V1 + Deserialize V3 with simple Default`` () =
let tenant : TenantV1 = { Name = "Doe" }
let json = JsonConvert.SerializeObject(tenant, serializationSettings)
let tenant = JsonConvert.DeserializeObject<TenantV3>(json, deserializationSettings)
tenant.Name |> should equal "Doe"
tenant.FirstName |> should equal "John"
[<Test>] // FAILS
let ``Serialize V1 + Deserialize V4 with complex Default`` () =
let tenant : TenantV1 = { Name = "Doe" }
let json = JsonConvert.SerializeObject(tenant, serializationSettings)
let tenant = JsonConvert.DeserializeObject<TenantV4>(json, deserializationSettings)
tenant.Name |> should equal "Doe"
tenant.Gender |> should equal Other // NULL
解决方案
虽然明显的问题是版本控制,但可能还有其他问题。JSON 是人类可读的,人类实际上可能会对其进行编辑并更改某些内容以使其无效(或您对它的假设)。我喜欢对 JsonConvert.DeserializeObject<>() 进行单行调用,但有时这还不够。我将用户设置存储在 JSON 中,并且完全按照您的建议进行,“注册一些在反序列化期间为给定类型提供默认值的函数”。
所以,我有一个记录类型“UserSettings”,它有很多不同类型的值。我没有明确附加版本号,因为处理丢失的成员并不真正需要它。但显然,每当我添加新设置时,版本都会发生变化。
对于 JSON 反序列化器返回的默认值不可接受的每个成员,我定义了一个函数来处理提供值,并且如果需要,还可以进行验证。
我对 JSON 文本进行了两次反序列化(尽管我只从磁盘读取了一次)。首先,我安全地(嗯,不完全安全,见下文)反序列化到字典中,然后收集这些键,以便我以后可以查找值是否来自反序列化器,因为它找到了它,或者因为它提供了默认值(因为它不存在)。
然后我第二次反序列化,这次是 UserSettings 的一个实例。然后,我根据反序列化器给我的实例构造一个新的 UserSettings 实例,但是对于我调用的每个成员(如果需要),该成员的特殊强制函数。
我创建了一个精简的示例,但它仍然很长,我将使用一个要点。
https://gist.github.com/jimfoye/8a5e99291e863f55d0b1f3b351f50e6d
请注意,有时反序列化器返回的默认值是可以的,有时不是,有时需要对值进行范围检查或后处理等。只需将您需要的任何内容放入处理该成员的函数中即可。
所有这些代码,如果(例如)定义为 bool 的值已被用户更改为无法解析为 bool 的值,会发生什么?这将搞砸对 DeserializeObject() 的第二次调用。您可以通过尝试对第一次调用 DeserializeObject() 返回的字符串值进行类型转换来进一步证明这一点。但是我决定在这种情况下直接弃用(很明显,我捕获了异常,只返回一组完整的默认值)。
推荐阅读
- swift - 'CGRectGetMaxX' 已被属性 'CGRect.maxX' 替换 swift 错误
- angular - Angularfire2 身份验证:如何获取当前用户?
- javascript - Node.js TypeError:路径必须是字符串或缓冲区
- python - 如何更改 kivy 小部件的默认值?
- apache - 重写发送到上一个目录,本地 index.php 未读取
- angular - 无法从我的 Angular 5 项目中访问 Spring Boot Rest API
- angular - Angular 6 + Chart.js - 根据条件显示来自不同组件的目标图表
- android - 如何将 androidx.recyclerview.widget.RecyclerView 与工具一起使用:listitem?
- r - 如何在抓取时停止重复相同的列名
- angular - Angular 6:如何订阅特定 json 响应键的数据