2014-02-13 39 views
5

我有一个简单的问题:是否有可能解析F#地图类型从?因为当我尝试它时(与F# Map<string, string>),它很容易序列化,它看起来它是如何,但是当我尝试反序列化它是抛出一个异常。反序列化F#地图由Json.Net

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" 
} 

它不序列化设置(仅缩进)。那么是否有可能对此进行序列化,或者是否有一些解决方法?

谢谢回答

+0

我想是有显著问题,因为F#图是不可改变的。 –

+1

https://gist.github.com/mausch/10022178 –

回答

1

的问题是,json.net无法构造一个Map<int,string>。但是,如果你反序列化为一个正常的.net Dictionary<int,string>它会工作,因为json是一样的。

0

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

这是F#地图的原始文件:(从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 

正如你看到的上面,地图没有默认构造函数,但串行需要默认构造函数的类。

序列化映射最好的方法就是绘制地图,以定期.NET字典,但随后新的字典里没有所有的F#的地图,尤其是F#的不变性的地图的优势。

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只是为了支持序列化。

+0

如果你发现自己打电话MakeGenericType了一下,然后就可以得心应手地创建一个辅助型简单的事情 - 看http://stackoverflow.com/a/30836070/136675 –

2

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

但是,如果仅停留在使用的早期版本,那么某些原因的达克斯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 |]) 
}