如何使用 Json.Net 将缺少的属性反序列化为 F# 记录中的空列表而不是 null

Sea*_*ron 2 f# json.net

考虑包含如下列表值的 F# 记录:

type MyRecord = {
    Name: string
    SomeList: string list
}
Run Code Online (Sandbox Code Playgroud)

Netwonsoft.Json.JsonConvert当 JSON 不包含记录的列表值的属性时,使用将 JSON 反序列化为Values该记录将导致反序列化的记录具有null列表的值而不是空列表[]

那是,

open Newtonsoft.Json
JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "Some name"}""" ) |> printfn "%A"
// Gives: { Name = "Some name"; SomeList = null; }
Run Code Online (Sandbox Code Playgroud)

如何反序列化 usingNetwonsoft.Json以使列表初始化为空列表?例如:

{ Name = "Some name"; SomeList = []; }
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 5

您可以使用自定义合同解析器来执行此操作,如下所示:

type ParameterizedConstructorInitializingContractResolver() =
    inherit DefaultContractResolver()

    // List is a module not a static class so it's a little inconvenient to access via reflection.  Use this wrapper instead.
    static member EmptyList<'T>() = List.empty<'T>

    override __.CreatePropertyFromConstructorParameter(matchingMemberProperty : JsonProperty, parameterInfo : ParameterInfo) =
        let property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo)
        if (not (matchingMemberProperty = null) && property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() = typedefof<_ list>) then
            let genericMethod = typeof<ParameterizedConstructorInitializingContractResolver>.GetMethod("EmptyList", BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static)
            let concreteMethod = genericMethod.MakeGenericMethod(property.PropertyType.GetGenericArguments())
            let defaultValue = concreteMethod.Invoke(null, null)
            property.DefaultValue <- defaultValue
            property.DefaultValueHandling <- new System.Nullable<DefaultValueHandling>(DefaultValueHandling.Populate)
            matchingMemberProperty.DefaultValue <- defaultValue
            matchingMemberProperty.DefaultValueHandling <- new System.Nullable<DefaultValueHandling>(DefaultValueHandling.Populate)
        property
Run Code Online (Sandbox Code Playgroud)

然后使用它如下:

let settings = JsonSerializerSettings(ContractResolver = new ParameterizedConstructorInitializingContractResolver())

let myrecord1 = JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "Missing SomeList"}""", settings )
let myrecord2 = JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "Populated SomeList", "SomeList" : ["a", "b", "c"]}""", settings )
let myrecord3 = JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "null SomeList", "SomeList" : null}""", settings )
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 合约解析器适用于通过参数化构造函数反序列化的任何对象,其中包括但不限于 f# 记录。如果任何此类对象具有类型T list为 any 的构造函数参数T,则该值将在缺失或为 null 时默认为List.empty<T>

  • 合约解析器List.empty<T>对所有反序列化对象重用默认值的相同实例,这在这里很好,因为 f# 列表是不可变的(而且List.empty<T>似乎是单例)。同样的方法不适用于为可变集合提供默认值作为构造函数参数。

  • 您可能希望缓存合约解析器以获得最佳性能

  • 构造函数参数必须与相应的属性具有相同的名称(模数大小写)。

演示小提琴在这里