F# 中使用可区分联合反序列化数据的另一个失败

2024-01-07

在一个问题之后,答案提供了一个工作解决方案来序列化/反序列化受歧视的联合(IgnoreMissingMember 设置似乎不适用于 FSharpLu.Json 反序列化器 https://stackoverflow.com/questions/62364229/ignoremissingmember-setting-doesnt-seem-to-work-with-fsharplu-json-deserializer/62364913#62364913)

我现在有一个失败的实际案例(尽管它在更简单的情况下有效)。

这是测试代码:

open System.Collections.Generic
open Microsoft.FSharpLu.Json
open Newtonsoft.Json
open Newtonsoft.Json.Serialization

// set up the serialization / deserialization based on answer from:
// https://stackoverflow.com/questions/62364229/ignoremissingmember-setting-doesnt-seem-to-work-with-fsharplu-json-deserializer/62364913#62364913

let settings =
    JsonSerializerSettings(
        NullValueHandling = NullValueHandling.Ignore,
        Converters = [| CompactUnionJsonConverter(true, true) |]
    )

let serialize object =
    JsonConvert.SerializeObject(object, settings)

let deserialize<'a> object =
    JsonConvert.DeserializeObject<'a>(object, settings)


// define the type used
type BookSide =
    | Bid
    | Ask

type BookEntry =
    {
        S : float
        P : float
    }

type BookSideData =
    Dictionary<int, BookEntry>

type BookData =
    {
        Data: Dictionary<BookSide, BookSideData>
    }

    static member empty =
        {
            Data = Dictionary<BookSide, BookSideData>(dict [ (BookSide.Bid, BookSideData()); (BookSide.Ask, BookSideData()) ])
        }

// make some sample data
let bookEntry = { S=3.; P=5. }
let bookData = BookData.empty
bookData.Data.[BookSide.Bid].Add(1, bookEntry)

// serialize. This part works
let s = serialize bookData

// deserialize. This part fails
deserialize<BookData> s

序列化的测试数据将如下所示:

{"Data":{"Bid":{"1":{"S":3.0,"P":5.0}},"Ask":{}}}

但反序列化会像这样崩溃:

无法将字符串“Bid”转换为字典键类型“FSI_0023+BookSide”。创建一个 TypeConverter 将字符串转换为键类型对象。

虽然 DU 的序列化/反序列化是通过 FSharpLu 进行的,FSharpLu 具有 DU 转换器。

我试图找到一些自动化解决方案,而不是编写自定义 TypeConverter (除了我从未这样做过的事实)的原因是,我有很多我无法控制的类型。

这是一个小提琴:https://dotnetfiddle.net/Sx0k4x https://dotnetfiddle.net/Sx0k4x


你的基本问题是你正在使用BookSide作为字典键——但这是一个 f# 联合,这使得它成为复杂键-- 不能立即与字符串进行转换。不幸的是,Json.NET 不支持开箱即用的复杂字典键,如其中所述序列化指南 https://www.newtonsoft.com/json/help/html/SerializationGuide.htm#Dictionarys:

序列化字典时,字典的键将转换为字符串并用作 JSON 对象属性名称。为键写入的字符串可以通过重写来自定义ToString()对于密钥类型或通过实施TypeConverter https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.typeconverter. A TypeConverter还支持在反序列化字典时再次将自定义字符串转换回来。

处理此问题有两种基本方法:

  1. 实施一个TypeConverter如所示,例如无法使用 Json.net 使用复杂键序列化字典 https://stackoverflow.com/q/24504245/3744182.

  2. 将字典序列化为键/值对对象的数组,例如如图所示将字典序列化为数组(键值对) https://stackoverflow.com/q/12751354/3744182.

由于您的数据模型包含字典各种键(DU、字符串和整数)第二种解决方案似乎是唯一的可能性。下列DictionaryConverter应该有必要的逻辑:

let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null)

type Type with
    member t.BaseTypesAndSelf() =
        t |> Seq.unfold (fun state -> if isNull state then None else Some(state, state.BaseType))
    member t.DictionaryKeyValueTypes() = 
        t.BaseTypesAndSelf()
            |> Seq.filter (fun i -> i.IsGenericType && i.GetGenericTypeDefinition() = typedefof<Dictionary<_,_>>)
            |> Seq.map (fun i -> i.GetGenericArguments())

type JsonReader with
    member r.ReadAndAssert() = 
        if not (r.Read()) then raise (JsonReaderException("Unexpected end of JSON stream."))
        r
    member r.MoveToContentAndAssert() =
        if r.TokenType = JsonToken.None then r.ReadAndAssert() |> ignore
        while r.TokenType = JsonToken.Comment do r.ReadAndAssert() |> ignore
        r

type internal DictionaryReadOnlySurrogate<'TKey, 'TValue>(i : IDictionary<'TKey, 'TValue>) =
    interface IReadOnlyDictionary<'TKey, 'TValue> with
        member this.ContainsKey(key) = i.ContainsKey(key)
        member this.TryGetValue(key, value) = i.TryGetValue(key, &value)
        member this.Item with get(index) = i.[index]
        member this.Keys = i.Keys :> IEnumerable<'TKey>
        member this.Values = i.Values :> IEnumerable<'TValue>
        member this.Count = i.Count
        member this.GetEnumerator() = i.GetEnumerator()
        member this.GetEnumerator() = i.GetEnumerator() :> IEnumerator        

type DictionaryConverter () =
    // ReadJson adapted from this answer https://stackoverflow.com/a/28633769/3744182
    // To https://stackoverflow.com/questions/28451990/newtonsoft-json-deserialize-dictionary-as-key-value-list-from-datacontractjsonse
    // By https://stackoverflow.com/users/3744182/dbc
    inherit JsonConverter()

    override this.CanConvert(t) = (t.DictionaryKeyValueTypes().Count() = 1) // If ever implemented for IReadOnlyDictionary<'TKey, 'TValue> then reject DictionaryReadOnlySurrogate<'TKey, 'TValue>

    member private this.ReadJsonGeneric<'TKey, 'TValue> (reader : JsonReader, t : Type, existingValue : obj, serializer : JsonSerializer) : obj =
        let contract = serializer.ContractResolver.ResolveContract(t)
        let dict = if (existingValue :? IDictionary<'TKey, 'TValue>) then existingValue :?> IDictionary<'TKey, 'TValue> else contract.DefaultCreator.Invoke() :?> IDictionary<'TKey, 'TValue>
        match reader.MoveToContentAndAssert().TokenType with 
        | JsonToken.StartArray -> 
            let l = serializer.Deserialize<List<KeyValuePair<'TKey, 'TValue>>>(reader)
            for p in l do dict.Add(p) 
            dict :> obj
        | JsonToken.StartObject ->
            serializer.Populate(reader, dict)
            dict :> obj
        | JsonToken.Null -> null // Or throw an exception if you prefer
        | _ -> raise (JsonSerializationException(String.Format("Unexpected token {0}", reader.TokenType)))

    override this.ReadJson(reader, t, existingValue, serializer) = 
        let keyValueTypes = t.DictionaryKeyValueTypes().Single(); // Throws an exception if not exactly one.
        let m = typeof<DictionaryConverter>.GetMethod("ReadJsonGeneric", BindingFlags.NonPublic ||| BindingFlags.Instance ||| BindingFlags.Public);
        m.MakeGenericMethod(keyValueTypes).Invoke(this, [| reader; t; existingValue; serializer |])

    member private this.WriteJsonGeneric<'TKey, 'TValue> (writer : JsonWriter, value : obj, serializer : JsonSerializer) =
        let dict = value :?> IDictionary<'TKey, 'TValue>
        let keyContract = serializer.ContractResolver.ResolveContract(typeof<'Key>)
        // Wrap the value in an enumerator or read-only surrogate to prevent infinite recursion.
        match keyContract with
        | :? JsonPrimitiveContract -> serializer.Serialize(writer, new DictionaryReadOnlySurrogate<'TKey, 'TValue>(dict)) 
        | _ -> serializer.Serialize(writer, seq { yield! dict }) 
        ()

    override this.WriteJson(writer, value, serializer) = 
        let keyValueTypes = value.GetType().DictionaryKeyValueTypes().Single(); // Throws an exception if not exactly one.
        let m = typeof<DictionaryConverter>.GetMethod("WriteJsonGeneric", BindingFlags.NonPublic ||| BindingFlags.Instance ||| BindingFlags.Public);
        m.MakeGenericMethod(keyValueTypes).Invoke(this, [| writer; value; serializer |])
        ()

您可以将其添加到设置中,如下所示:

let settings =
    JsonSerializerSettings(
        NullValueHandling = NullValueHandling.Ignore,
        Converters = [| CompactUnionJsonConverter(true, true); DictionaryConverter() |]
    )

并为您生成以下 JSONbookData:

{
  "Data": [
    {
      "Key": "Bid",
      "Value": [
        {
          "Key": 1,
          "Value": {
            "S": 3.0,
            "P": 5.0
          }
        }
      ]
    },
    {
      "Key": "Ask",
      "Value": []
    }
  ]
}

Notes:

  • 该转换器适用于所有人Dictionary<TKey, TValue>类型(和子类型)。

  • 转换器检测字典键是否将使用原始契约进行序列化,如果是,则将字典紧凑地序列化为 JSON 对象。如果不是,字典将被序列化为数组。您可以在上面显示的 JSON 中观察到这一点:Dictionary<BookSide, BookSideData>字典被序列化为 JSON 数组,并且Dictionary<int, BookEntry>字典被序列化为 JSON 对象。

    在反序列化期间,转换器会检测传入的 JSON 值是数组还是对象,并根据需要进行调整。

  • 该转换器仅针对可变 .Net 实现Dictionary<TKey, TValue>类型。逻辑需要一些轻微的修改才能反序列化不可变的Map<'Key,'Value> https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.map%5b%27key%2c%27value%5d-class-%5bfsharp%5d type.

演示小提琴here https://dotnetfiddle.net/X8zxmI.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

F# 中使用可区分联合反序列化数据的另一个失败 的相关文章

  • 在 Mono 中反序列化 JSON 数据

    使用 Monodroid 时 是否有一种简单的方法可以将简单的 JSON 字符串反序列化为 NET 对象 System Json 只提供序列化 不提供反序列化 我尝试过的各种第三方库都会导致 Mono Monodroid 出现问题 谢谢 f
  • 在java中是否可以使用反射创建没有无参数构造函数的“空白”类实例?

    我有一个没有默认构造函数的类 我需要一种方法来获取此类的 空白 实例 空白 意味着实例化后所有类字段都应具有默认值 如 null 0 等 我问这个问题是因为我需要能够序列化 反序列化大对象树 而且我无法访问该对象类的源 并且类既没有默认构造
  • 使用多态对象数组进行 JSON 反序列化

    我在涉及多态对象数组的 JSON 反序列化方面遇到问题 我已经尝试过记录的序列化解决方案here https stackoverflow com questions 5186973 json serialization of array w
  • 非 Web 项目的 XML 序列化程序集

    我正在尝试解决 VS 2010 VB NET 和 C 中自动生成序列化程序集的众所周知的问题 项目设置中的 生成序列化程序集 选项对于非 Web 项目没有任何作用 请参阅http blog devstone com aaron archiv
  • 为什么无法在 F# 项目中添加子文件夹?

    在大多数 NET项目中 我可以使用文件夹来组织代码文件 在 C 中 我不能 但过滤器最终会扮演相同的角色 但是 在 Visual Studio 2010 中的 F 中 我不能 每个代码文件都直接显示在项目目录中 为什么这个功能不可用 组织包
  • 我可以在 RestEasy 中指定用于方法结果转换的 jackson @JsonView 吗?

    我正在使用基于的序列化模型 JsonView 我通常配置杰克逊ContextResolver像这样 Override public ObjectMapper getContext Class
  • 如何使 sgen.exe 保留程序集的版本?

    我想为我的程序集创建一个序列化程序集 sgen做得很好 但我不知道如何让它为序列化程序集分配与源程序集相同的版本 有任何想法吗 sgen似乎默认采用源程序集版本 这是相当合理的 这是我的运行方式 没有什么特别的 PathToSDK Micr
  • F# 中的自定义路由事件

    我正在尝试翻译这段 C 代码 https msdn microsoft com en us library ms752288 aspx 到目前为止我的尝试 type MyButtonSimple as self inherit Button
  • 您可以使用 .net core 运行 F# 脚本文件 (.fsx) 吗?

    是否可以使用 net core 运行 fsx 文件 相当于fsharpi在单声道上 它在 NETCore v3 0 或更高版本中开箱即用 cat hello fsx usr bin env fsharpi printfn hello wor
  • 反序列化 HTTP POST 参数

    我正在尝试找到一种更原生或更优雅的解决方案 用于将 HTTP POST 参数反序列化为相应的对象 目前 我将字符串转换为字典 然后将其序列化为 JSON 然后将其反序列化为我的最终对象 参数字符串示例 TotalCost 0 01200 D
  • 地图中的一组键

    我有一个地图 X 我试图获取一组满足特定条件的键 如下所示 Map Keys X gt Set filter fun x gt 但我找不到从 F 的 Map 集合中获取密钥的方法 转换你的map http msdn microsoft co
  • 保存数据的最佳方法

    我创建了一个课程 我想在其中跟踪学生的统计数据 我打算稍后制作一个 GUI 来操作这些数据 我的主要问题是 保存和稍后检索这些数据的最佳方法是什么 我读过有关 pickle 和 JSON 的内容 但我并不真正了解它们是如何工作的 特别是它们
  • 基于函数签名的模式匹配

    在 F 中 您可以对函数签名进行模式匹配 我想用一个函数来装饰多个函数 该函数测量函数的执行情况并调用 statsd 我当前的功能是 let WrapFunctionWithPrefix metrics Metric Client IRec
  • 使用 catch all 字典属性将 json 序列化为对象

    我想使用 JSON net 反序列化为对象 但将未映射的属性放入字典属性中 是否可以 例如给定 json one 1 two 2 three 3 和 C 类 public class Mapped public int One get se
  • C# 反序列化过程中创建指向父对象的指针

    我有这样的课程 Serializable public class child public Parent parent Serializable public class Parent public List
  • 在 C# 中使用枚举值反序列化 Dictionary

    我正在尝试序列化 反序列化Dictionary
  • DataContractJsonSerializer 包含元素类型子类型的通用列表

    我要使用DataContractJsonSerializer用于 JSON 序列化 反序列化 我在 JSON 数组中有两种对象类型 并希望将它们都反序列化为相应的对象类型 具有以下类定义 DataContract public class
  • 如何解析多态 JSON 数组?

    我有一个 JSON 格式的文件 其中包含个人用户的记录 一些用户的记录中间有一个评论字段 我只想解析顶级项目 全名 贡献者姓名 电子邮件 使用 Newtonsoft JSON 解析器 但我似乎无法让它识别单个对象 当我将整个字符串解析为一个
  • 将 JSON 反序列化为自定义列表

    我有这个 json var x 99 abc 2dp GroupNum 0 Total 4 1 7 x date 60 x 1dp GroupNum 1 存在以下规则 让i参考内部列表索引 x i 0 必填项 始终为整数 x i 1 必填项
  • 使用反射的属性类型或类

    我想知道是否可以确定对象属性的类或原始类型 获取所有属性名称和值非常容易 所以答案 https stackoverflow com questions 2299841 objective c introspection reflection

随机推荐