升级到.NET 6后F#中Newtonsoft.Json反序列化失效问题
问题背景
以下是F#类型定义:
[<DataContract>] type AreaCodeSingleResponse = { [<field:DataMember(Name = "id")>] Id : string [<field:DataMember(Name = "code")>] Code : string [<field:DataMember(Name = "name")>] Name : string } [<DataContract>] type AreaCodeListResponse = { [<field:DataMember(Name = "areacode_list")>] AreaCodeList : List<AreaCodeSingleResponse> }
对应的JSON数据示例:
{ "areacode_list": [ { "id": "00447", "code": "07", "name": "UK Mobile", "order_column": "_00447" }, { "id": "0044113", "code": "0113", "name": "Leeds", "order_column": "0044113" } ] }
反序列化代码(调用时传入AreaCodeListResponse类型):
member __.ReturnDataToType<'T>() = __.ReturnData |> JsonConvert.DeserializeObject<'T>
升级到.NET 6前代码可正常返回数据列表,但升级后反序列化失败,返回空列表且无报错,所有同类调用均失效。
.NET 6相关变更点及问题原因
- F#记录类型的字段封装性增强:.NET 6中,F#记录的字段默认以
init-only属性形式生成,而非之前版本的普通私有字段。Newtonsoft.Json默认配置无法正确识别标记了[<field:DataMember>]的init-only字段,导致字段映射失败。 - Newtonsoft.Json兼容性调整:在.NET 6环境下,Newtonsoft.Json对F#类型的序列化/反序列化默认行为发生变化,未显式配置的情况下,无法直接访问F#记录的私有字段(即使标记了DataMember属性),进而无法完成数据绑定。
- 隐式行为变更:.NET 6之前,F#记录的私有字段可被Newtonsoft.Json通过反射直接访问,而.NET 6强化了类型封装,默认禁止此类访问,导致反序列化时无法赋值。
解决方案
方案1:调整Newtonsoft.Json配置
显式配置序列化设置,添加F#类型兼容的解析器与转换器:
open Newtonsoft.Json open Newtonsoft.Json.Serialization member __.ReturnDataToType<'T>() = let settings = JsonSerializerSettings( ContractResolver = CamelCasePropertyNamesContractResolver(), MissingMemberHandling = MissingMemberHandling.Ignore ) // 若用到F# Option类型,添加对应转换器 settings.Converters.Add(FSharpOptionConverter()) __.ReturnData |> JsonConvert.DeserializeObject<'T>(settings)
方案2:修改F#记录的属性标记
将[<field:DataMember>]替换为直接标记在属性上,让Newtonsoft.Json直接处理公共属性:
[<DataContract>] type AreaCodeSingleResponse = { [<DataMember(Name = "id")>] Id : string [<DataMember(Name = "code")>] Code : string [<DataMember(Name = "name")>] Name : string } [<DataContract>] type AreaCodeListResponse = { [<DataMember(Name = "areacode_list")>] AreaCodeList : List<AreaCodeSingleResponse> }
方案3:改用.NET 6原生System.Text.Json
使用.NET 6内置序列化库,配置兼容F#的选项:
open System.Text.Json member __.ReturnDataToType<'T>() = let options = JsonSerializerOptions( PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase ) __.ReturnData |> JsonSerializer.Deserialize<'T>(options)
内容的提问来源于stack exchange,提问作者John McArthur
相关产品推荐
相关产品推荐

