2014-02-13 11 views
5

Ich habe eine einfache Frage: Ist es möglich, F # Map type von zu analysieren? Denn wenn ich es versuche (mit F# Map<string, string>), ist es einfach zu serialisieren und es sieht so aus, wie es sein muss, aber wenn ich versuche es zu deserialisieren, gibt es eine Ausnahme.Deserializing F # Map von 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 

Und es ist Deserialisieren von klassisch:

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

Die resultierende JSON sieht aus wie C# Wörterbuch

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

Es wird ohne Einstellungen Serialisierung (Nur Vertiefung). Also ist es möglich, dies zu serialisieren, oder gibt es eine Workaround?

Vielen Dank für Antwort

+0

Ich stelle mir vor, dass es ein signifikantes Problem gibt, da die F # -Karte unveränderlich ist. –

+1

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

Antwort

1

Das Problem ist, dass json.net kein Map<int,string> konstruieren kann. Wenn Sie jedoch zu einem regulären .net Dictionary<int,string> deserialisieren, wird es funktionieren, da der JSON gleich ist.

0

Sie können F # 's Map nicht direkt serialisieren, da es keinen Standardkonstruktor (Konstruktor ohne Parameter) gibt.

Dies ist die ursprüngliche Dokumentation # map F: (von 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 

Wie Sie oben sehen, Karte nicht Standardkonstruktor hat, sondern der Serializer eine Klasse mit Standard-Konstruktor benötigen.

Der beste Weg, um eine Karte zu serialisieren, ist die Zuordnung der Map zum regulären .NET-Wörterbuch, aber dann hat das neue Wörterbuch nicht alle Vorteile von F # 's Map, insbesondere die Unveränderlichkeit von F #' s Map.

3

Sie können Ihren eigenen Konverter dazu machen. Es ist eine Menge über Reflexion und die Konstruktion von geeigneten generischen Typen, aber es kann getan werden.

Sie zuerst deserialize zu einem Dictionary<Key, Val>, dann erstellen und eine List<Tuple<Key, Val>> manuell über Reflexion füllen (weil der Map Konstruktor Tuples erfordert, nicht KeyValuePairs), dann passieren schließlich, dass in den Map Konstruktor.

Nicht sicher, ob es ein einfacher Weg ist, aber das ist, was ich kam mit:

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

Sobald Sie Ihren Konverter haben, dann nur Sie es in die DeserializeObject Methode übergeben und JsonConvert wird es verwenden, wo immer angemessen .

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

Das schöne an es auf diese Weise ist zu tun, wenn Sie eine große/tiefe Aufzeichnung haben, wo Ihr Map nur ein einzelnes Feld ist, dann wird es auch mit, dass Arbeit - Sie dies nicht tun müssen Sie Ihre Datensatzstruktur ändern, um Dictionaries nur zur Unterstützung der Serialisierung zu verwenden.

+0

Wenn Sie sich ein wenig MakeGenericType nennen, dann kann es hilfreich sein, einen Helfertyp für einfache Dinge zu erstellen - siehe http://stackoverflow.com/a/30836070/136675 –

2

Diese Funktionalität wurde Teil von JSON.Net in Version 6.0.3.(30. April 2014)

Aber, wenn Sie aus irgendeinem Grund stecken mit einer früheren Version dann eine vereinfachte (und effizienter als weniger Reflexion) Version von Dax Fohl Version könnte sein:

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 |]) 
}