You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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]'.

原因分析

  1. Json.NET默认将PersistentHashMap识别为可枚举集合,尝试按列表类型反序列化,但PersistentHashMap并非列表实现,导致第一个异常。
  2. 通用泛型转换器在注册时,若未绑定具体类型参数,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 13:41:30