【问题标题】:How to deserialise a missing property to an empty list in an F# record instead of null using Json.Net如何使用 Json.Net 将缺失的属性反序列化为 F# 记录中的空列表而不是 null
【发布时间】:2020-04-06 04:25:07
【问题描述】:

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

type MyRecord = {
    Name: string
    SomeList: string list
}

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

也就是说,

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

如何使用Netwonsoft.Json 反序列化,以便将列表初始化为空列表?例如:

{ Name = "Some name"; SomeList = []; }

【问题讨论】:

    标签: f# json.net


    【解决方案1】:

    您可以使用custom contract resolver 执行此操作,如下所示:

    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
    

    然后按如下方式使用:

    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 )
    

    注意事项:

    • 合约解析器适用于 deserialized via a parameterized constructor 的任何对象,包括但不限于 f# 记录。如果任何此类对象具有类型为T list 的任何T 的构造函数参数,则该值将默认为List.empty&lt;T&gt; 缺失或为空。

    • 合约解析器为所有反序列化对象重用默认值 List.empty&lt;T&gt; 的相同实例,这在此处很好,因为 f# 列表是不可变的(并且 List.empty&lt;T&gt; 似乎无论如何都是单例)。同样的方法不适用于为可变集合提供默认值作为构造函数参数。

    • 您可能想cache the contract resolver for best performance

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

    演示小提琴here.

    【讨论】:

    • 太棒了 - 谢谢@dbc,也感谢您提供的详细说明!
    猜你喜欢
    • 2015-02-16
    • 2016-02-29
    • 2017-03-21
    • 1970-01-01
    • 2023-03-12
    • 2020-06-08
    • 2021-05-26
    相关资源
    最近更新 更多