.net之使用 Json.NET 为 F# 记录标签填充复杂的默认值

落叶无声 阅读:100 2025-06-02 22:19:02 评论:0

假设我有一个记录的第一个版本,我正在对其进行序列化和反序列化以保留它所代表的数据。现在我想为该记录添加一个新标签。由于 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() 返回的字符串值进行类型转换来进一步证明这一点。但我决定在这种情况下只是下注(显然,我捕获了异常,并且只返回了一组完整的默认值)。


标签:json
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号