如何用Json.NET反序列化FSharpX的PersistentHashMap<'k, 'v>
使用Json.NET反序列化FSharpx.Collections.PersistentHashMap<'k, 'v>的通用解决方案
问题重现
尝试用Json.NET序列化和反序列化FSharpx的PersistentHashMap<'k, 'v>时,序列化可正常生成数组格式JSON,但反序列化抛出异常:
测试代码
open FSharpx.Collections open Newtonsoft.Json let map = PersistentHashMap.add 1 1 PersistentHashMap.empty let ser = JsonConvert.SerializeObject(map, Formatting.Indented) printfn "%s" ser let des = JsonConvert.DeserializeObject<PersistentHashMap<_, _>>(ser)
序列化结果
[ { "Item1": 1, "Item2": 1 } ]
反序列化异常
Exception thrown: 'Newtonsoft.Json.JsonSerializationException' in Newtonsoft.Json.dll An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code Cannot create and populate list type FSharpx.Collections.PersistentHashMap`2[System.Int32,System.Int32]. Path '', line 1, position 1.
尝试编写通用转换器时,又遇到类型转换异常:
type PersistentHashMapConverter<'k, 'v>() = inherit JsonConverter() override _.CanConvert(objType: Type) = objType = typeof<PersistentHashMap<'k, 'v>> override _.WriteJson(writer: JsonWriter, value: obj, serializer: JsonSerializer) = let map = value :?> PersistentHashMap<'k, 'v> serializer.Serialize(writer, map |> Seq.toList) override _.ReadJson(reader: JsonReader, objType: Type, existingValue: obj, serializer: JsonSerializer) = if reader.TokenType <> JsonToken.Null then if reader.TokenType = JsonToken.StartArray then let token = JToken.Load reader let xs = token.ToObject<List<'k * 'v>>() (PersistentHashMap.empty, xs) ||> Seq.fold (fun acc (k, v) -> PersistentHashMap.add k v acc) :> obj else PersistentHashMap.empty :> obj else null
新异常
Exception thrown: 'System.InvalidCastException' in Newtonsoft.Json.dll An exception of type 'System.InvalidCastException' occurred in Newtonsoft.Json.dll but was not handled in user code Unable to cast object of type 'FSharpx.Collections.PersistentHashMap`2[System.Object,System.Object]' to type 'FSharpx.Collections.PersistentHashMap`2[System.Int32,System.Int32]'.
原因分析
- Json.NET默认将
PersistentHashMap识别为可枚举集合,尝试按列表类型反序列化,但PersistentHashMap并非列表实现,导致第一个异常。 - 通用泛型转换器在注册时,若未绑定具体类型参数,Json.NET会将
'k和'v默认解析为object,生成PersistentHashMap<obj, obj>实例,无法强制转换为具体的PersistentHashMap<int, int>,引发类型转换异常。
通用解决方案
编写一个非泛型的JsonConverter,通过反射动态处理任意PersistentHashMap<'k, 'v>类型:
完整转换器实现
open System open FSharpx.Collections open Newtonsoft.Json open Newtonsoft.Json.Linq type PersistentHashMapConverter() = inherit JsonConverter() // 判断是否为PersistentHashMap类型 override _.CanConvert(objType: Type) = objType.IsGenericType && objType.GetGenericTypeDefinition() = typeof<PersistentHashMap<_, _>> // 序列化:将PersistentHashMap转为键值对列表 override _.WriteJson(writer: JsonWriter, value: obj, serializer: JsonSerializer) = let seqType = typeof<seq<KeyValuePair<_,_>>>.MakeGenericType(objType.GetGenericArguments()) let keyValueSeq = value.GetType().GetMethod("ToSeq").Invoke(value, [||]) serializer.Serialize(writer, keyValueSeq) // 反序列化:从键值对列表构建PersistentHashMap override _.ReadJson(reader: JsonReader, objType: Type, existingValue: obj, serializer: JsonSerializer) = if reader.TokenType = JsonToken.Null then null else // 获取类型参数k和v let keyType = objType.GetGenericArguments().[0] let valueType = objType.GetGenericArguments().[1] // 定义键值对列表的类型 let kvListType = typeof<List<KeyValuePair<_,_>>>.MakeGenericType(keyType, valueType) // 读取JSON为键值对列表 let token = JToken.Load(reader) let kvList = token.ToObject(kvListType, serializer) // 获取PersistentHashMap的empty和add方法 let emptyMethod = typeof<PersistentHashMap<_,_>>.GetMethod("get_Empty").MakeGenericMethod(keyType, valueType) let addMethod = typeof<PersistentHashMap<_,_>>.GetMethod("add").MakeGenericMethod(keyType, valueType) // 初始化空map并逐个添加键值对 let emptyMap = emptyMethod.Invoke(null, [||]) let enumerator = kvListType.GetMethod("GetEnumerator").Invoke(kvList, [||]) :?> System.Collections.IEnumerator let mutable result = emptyMap while enumerator.MoveNext() do let kv = enumerator.Current :?> System.Collections.Generic.KeyValuePair<obj, obj> result <- addMethod.Invoke(null, [| kv.Key; kv.Value; result |]) result
使用方法
注册转换器后即可自动处理任意PersistentHashMap<'k, 'v>类型,包括嵌套在复杂类型中的实例:
open Newtonsoft.Json // 配置Json.NET设置,添加转换器 let settings = JsonSerializerSettings() settings.Converters.Add(PersistentHashMapConverter()) // 序列化 let map = PersistentHashMap.add 1 "test" PersistentHashMap.empty let ser = JsonConvert.SerializeObject(map, Formatting.Indented, settings) // 反序列化 let des = JsonConvert.DeserializeObject<PersistentHashMap<int, string>>(ser, settings) // 复杂类型测试 type ComplexType = { Id: int Data: PersistentHashMap<string, float> } let complexObj = { Id = 100 Data = PersistentHashMap.add "price" 99.9f PersistentHashMap.empty } let complexSer = JsonConvert.SerializeObject(complexObj, Formatting.Indented, settings) let complexDes = JsonConvert.DeserializeObject<ComplexType>(complexSer, settings)
说明
- 该转换器通过反射动态适配任意
PersistentHashMap<'k, 'v>类型,无需为每种键值类型单独编写转换器。 - 序列化时将
PersistentHashMap转为KeyValuePair<'k, 'v>序列,保证JSON格式与F#标准Map<'k, 'v>一致,兼容性更好。 - 反序列化时通过反射调用
PersistentHashMap.empty和add方法,动态构建目标类型的实例,避免类型转换异常。
内容的提问来源于stack exchange,提问作者StefCor
相关产品推荐
相关产品推荐

