2014-02-13 3 views
5

У меня есть один простой вопрос: возможно ли разобрать F # Тип карты от ? Потому что, когда я пытаюсь это сделать (с F# Map<string, string>), его легко сериализовать, и он выглядит так, как он должен, но когда я пытаюсь десериализовать, он бросает исключение.Deserializing 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>. Однако, если вы deserialize к регулярному .net Dictionary<int,string>, он будет работать, так как json такой же.

0

Вы не можете напрямую сериализовать карту F #, так как у нее нет конструктора по умолчанию (конструктор без параметров).

Это оригинальная документация 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

Вы можете сделать свой собственный конвертер, чтобы сделать это. Это много размышлений и построения соответствующих общих типов, но это можно сделать.

Вы первый Deserialize к 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

Эта функциональность стала частью JSON.Net в версии 6.0.3.(30 апреля 2014)

Но, если вы застряли на какой-то причине с помощью более ранней версии, то упрощенная (и более эффективной, менее отражения) версия версии 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 |]) 
} 
Смежные вопросы