【问题标题】:Deserializing F# Map by Json.Net通过 Json.Net 反序列化 F# Map
【发布时间】:2014-02-13 07:19:59
【问题描述】:

我有一个简单的问题:是否可以从 解析 F# Map 类型?因为当我尝试它时(使用F# Map<string, string>),它很容易序列化并且看起来它必须如何,但是当我尝试反序列化时它会抛出异常。

Newtonsoft.Json.JsonSerializationException: Unable to find a default constructor to use for type Microsoft.FSharp.Collections.FSharpMap`2[System.Int32,System.String]. Path '1', line 2, position 7.
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewDictionary (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonDictionaryContract contract, System.Boolean& createdFromNonDefaultConstructor) [0x00000] in <filename unknown>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <filename unknown>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <filename unknown>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, Boolean checkAdditionalContent) [0x00000] in <filename unknown>:0

它是从经典反序列化的:

Map.ofList [ ("1", "one"); ("2", "two"); ("3", "three") ]

生成的 JSON 看起来像 C# 字典

{
  "1": "one",
  "2": "two",
  "3": "three"
}

它在没有设置的情况下进行序列化(只有缩进)。那么是否可以序列化呢,还是有一些解决方法?

感谢回答

【问题讨论】:

标签: json json serialization f# json.net


【解决方案1】:

您可以制作自己的转换器来执行此操作。需要大量反思和构造适当的泛型类型,但可以做到。

您首先反序列化为Dictionary&lt;Key, Val&gt;,然后通过反射手动创建并填充List&lt;Tuple&lt;Key, Val&gt;&gt;(因为Map 构造函数需要Tuples,而不是KeyValuePairs),最后将其传递给Map构造函数。

不确定是否有更简单的方法,但这是我想出的:

open System
open System.Collections
open System.Collections.Generic
open Newtonsoft.Json

let mapConverter = {
  new JsonConverter() with

    override x.CanConvert(t:Type) =
      t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Map<_, _>>

    override x.WriteJson(writer, value, serializer) =
      serializer.Serialize(writer, value)

    override x.ReadJson(reader, t, _, serializer) =
      let genArgs = t.GetGenericArguments()
      let generify (t:Type) = t.MakeGenericType genArgs
      let tupleType = generify typedefof<Tuple<_, _>>
      let listType = typedefof<List<_>>.MakeGenericType tupleType
      let create (t:Type) types = (t.GetConstructor types).Invoke
      let list = create listType [||] [||] :?> IList
      let kvpType = generify typedefof<KeyValuePair<_, _>>
      for kvp in serializer.Deserialize(reader, generify typedefof<Dictionary<_, _>>) :?> IEnumerable do
        let get name = (kvpType.GetProperty name).GetValue(kvp, null)
        list.Add (create tupleType genArgs [|get "Key"; get "Value"|]) |> ignore        
      create (generify typedefof<Map<_, _>>) [|listType|] [|list|]
}

获得转换器后,只需将其传递给 DeserializeObject 方法,JsonConvert 就会在适当的地方使用它。

let str = JsonConvert.SerializeObject (Map<_, _> [333, 1234])
JsonConvert.DeserializeObject<Map<int, int>>(str, mapConverter)

这样做的好处是,如果您有一个很大/很深的记录,而您的 Map 只是一个字段,那么它也可以使用它——您不必去改变你的记录结构来使用Dictionaries只是为了支持序列化。

【讨论】:

【解决方案2】:

此功能在 6.0.3 版本中成为 JSON.Net 的一部分。 (2014 年 4 月 30 日)

但是,如果您由于某种原因无法使用较早的版本,那么 Dax Fohl 版本的简化(并且由于反射较少而更有效)版本可能是:

type mapConvert<'f,'t when 'f : comparison>() =
    static member readJson (reader:JsonReader, serializer:JsonSerializer) =
        serializer.Deserialize<Dictionary<'f, 't>> (reader)
        |> Seq.map (fun kv -> kv.Key, kv.Value)
        |> Map.ofSeq

let mapConverter = {
  new JsonConverter() with
    override __.CanConvert (t:Type) =
      t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Map<_, _>>

    override __.WriteJson (writer, value, serializer) =
      serializer.Serialize(writer, value)

    override __.ReadJson (reader, t, _, serializer) =
      let converter = 
        typedefof<mapConvert<_,_>>.MakeGenericType (t.GetGenericArguments())

      let readJson =
        converter.GetMethod("readJson")

      readJson.Invoke(null, [| reader; serializer |])
}

【讨论】:

    【解决方案3】:

    问题是json.net 无法构造Map&lt;int,string&gt;。但是,如果您反序列化为常规的 .net Dictionary&lt;int,string&gt; 它将起作用,因为 json 是相同的。

    【讨论】:

      【解决方案4】:

      你不能直接序列化 F# 的 Map,因为它根本没有默认构造函数(没有参数的构造函数)。

      这是F# map的原始文档:(来自http://msdn.microsoft.com/en-us/library/ee353686%28v=vs.110%29.aspx

      [<Sealed>]
      type Map<[<EqualityConditionalOnAttribute>] 'Key,[<ComparisonConditionalOnAttribute>] [<EqualityConditionalOnAttribute>] 'Value (requires comparison)> =
          class
      interface IEnumerable
      interface IComparable
      interface IEnumerable
      interface ICollection
      interface IDictionary
      new Map : seq<'Key * 'Value> -> Map< 'Key, 'Value>
      member this.Add : 'Key * 'Value -> Map<'Key, 'Value>
      member this.ContainsKey : 'Key -> bool
      member this.Remove : 'Key -> Map<'Key, 'Value>
      member this.TryFind : 'Key -> 'Value option
      member this.Count :  int
      member this.IsEmpty :  bool
      member this.Item ('Key) : 'Value
      end
      

      如上所示,Map 没有默认构造函数,但序列化程序需要一个具有默认构造函数的类。

      序列化映射的最佳方法是将映射映射为常规 .NET 字典,但新字典并没有 F# Map 的所有优点,尤其是 F# Map 的不变性。

      【讨论】:

        猜你喜欢
        • 2020-10-03
        • 2019-05-20
        • 1970-01-01
        • 2012-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多