使用Json.Net反序列化F# Map

5
我有一个简单的问题:是否可能从解析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"
}

它是在没有设置的情况下进行序列化的(仅缩进)。那么是否可能对其进行序列化,或者是否有一些有效的解决方法?
谢谢回答。

我想由于F#映射是不可变的,所以存在一个重大问题。 - John Palmer
1
https://gist.github.com/mausch/10022178 - Mauricio Scheffer
4个回答

3
您可以自己制作转换器来完成此操作。这需要大量反射和构造适当的通用类型,但是可以完成。
您首先将其反序列化为一个Dictionary<Key,Val>,然后通过反射手动创建和填充List<Tuple<Key,Val>>(因为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来支持序列化。

如果你发现自己经常调用MakeGenericType,那么创建一个帮助类型来简化操作会很方便-请参见https://dev59.com/53zaa4cB1Zd3GeqPQHFU#30836070。 - Paul Westcott

2

这个功能在JSON.Net 6.0.3版本中成为了一部分。(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 |])
}

1
问题在于json.net无法构建一个Map<int,string>。但是,如果您反序列化为常规的.NET Dictionary<int,string>,它将起作用,因为JSON是相同的。

0

你不能直接序列化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没有默认构造函数,但序列化程序需要一个具有默认构造函数的类。

将Map序列化为常规的.NET字典是最好的方法,但新字典不具备F# Map的所有优点,特别是F# Map的不可变性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接