我有一个简单的问题:是否可以从json解析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"
}
它没有设置序列化(只有缩进)。那么可以序列化这个,还是有一些可行的解决方法?
感谢您的回答
答案 0 :(得分: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
来支持序列化。
答案 1 :(得分:2)
此功能在V6.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 |])
}
答案 2 :(得分:1)
问题是json.net无法构造Map<int,string>
。但是,如果您反序列化为常规.net Dictionary<int,string>
,它将起作用,因为json是相同的。
答案 3 :(得分: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没有默认构造函数,但序列化程序需要一个带默认构造函数的类。
序列化地图的最佳方法是将地图映射为常规的.NET字典,但新字典不具备F#Map的所有优点,尤其是F#Map的不变性。