Как десериализовать отсутствующее свойство в пустой список в записи F # вместо нуля, используя Json. Net - PullRequest
1 голос
/ 05 апреля 2020

Рассмотрим запись F #, которая содержит значение списка, например:

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

Использование Netwonsoft.Json.JsonConvert для десериализации JSON этой записи, когда JSON не содержит свойства для списка значение Values записи приведет к тому, что десериализованная запись будет иметь значение null для списка вместо пустого списка [].

То есть

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

Как Можно ли десериализовать, используя Netwonsoft.Json, чтобы список инициализировался в пустой список? Например:

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

1 Ответ

4 голосов
/ 05 апреля 2020

Вы можете сделать это с помощью пользовательского распознавателя контракта , такого как:

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 )

Примечания:

  • Средство разрешения контрактов работает для любого объекта, который десериализован с помощью параметризованного конструктора , который включает, но не ограничивается, записи f #. Если у любого такого объекта есть аргумент конструктора с типом T list для любого T, то по умолчанию значение будет равно List.empty<T>, если оно отсутствует или равно нулю.

  • Средство распознавания контрактов использует то же самое экземпляр значения по умолчанию List.empty<T> для всех десериализованных объектов, что здесь хорошо, так как списки f # неизменяемы (и в любом случае List.empty<T> кажется одноэлементным). Тот же подход не будет работать для предоставления значения по умолчанию для изменяемых коллекций в качестве аргумента конструктора.

  • Возможно, вы захотите кэшировать распознаватель контрактов для лучшей производительности .

  • Параметр конструктора должен иметь то же имя (регистр по модулю), что и соответствующее свойство.

Демонстрационная скрипка здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...