Используя пользовательский ContractResolver, как установить значение по умолчанию вместо нуля при десериализации пустого свойства JSON для члена типа значения? - PullRequest
0 голосов
/ 11 сентября 2018

Вот что я получил до сих пор. Спасибо Брайану Роджерсу :

public class JsonSerializeTest
{
    [Fact]
    public void deserialize_test()
    {
        var settings = new JsonSerializerSettings { ContractResolver = new CustomContractResolver() };

        var jsonString = "{\"PropertyA\":\"Test\",\"PropertyB\":null}";
        var jsonObject = JsonConvert.DeserializeObject<NoConfigModel>(jsonString, settings);
        Assert.NotNull(jsonObject);

    }
}

public class NoConfigModel
{
    public string PropertyA { get; set; }
    public int PropertyB { get; set; }
    public bool PropertyC { get; set; }

}

class CustomContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        property.ShouldDeserialize = instance =>
        {
            try
            {
                PropertyInfo prop = (PropertyInfo)member;
                if (prop.CanRead)
                {
                    var value = prop.GetValue(instance, null);// getting default value(0) here instead of null for PropertyB
                    return value != null;
                }
            }
            catch
            {
            }
            return false;
        };
        return property;
    }
}

Моя проблема:

Необходимо установить значение по умолчанию для полей, не допускающих значения Nullable, вместо Exception или для целого объекта, равного NULL. Отсутствие пропущенного значения не является проблемой (дает значение по умолчанию DefaultContractResolver), но когда в json явно устанавливается значение NULL, равное NULL, то это дает исключение.

Мой код выше близок, но недостаточно близок. Я думаю, что мне нужно найти способ узнать, что значение фактически равно нулю от json и установить ShouldDeserialize =false для этих случаев.

1 Ответ

0 голосов
/ 11 сентября 2018

Вы хотите, чтобы во время десериализации, когда встречается значение null для ненулевого элемента, чтобы установить значение по умолчанию (ненулевое) обратно в содержащий объект.Это можно сделать, переопределив DefaultContractResolver.CreateProperty следующим образом:

class CustomContractResolver : DefaultContractResolver
{
    class NullToDefaultValueProvider : ValueProviderDecorator
    {
        readonly object defaultValue;

        public NullToDefaultValueProvider(IValueProvider baseProvider, object defaultValue) : base(baseProvider)
        {
            this.defaultValue = defaultValue;
        }

        public override void SetValue(object target, object value)
        {
            base.SetValue(target, value ?? defaultValue);
        }
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        if (property != null && property.PropertyType.IsValueType && Nullable.GetUnderlyingType(property.PropertyType) == null && property.Writable)
        {
            var defaultValue = property.DefaultValue ?? Activator.CreateInstance(property.PropertyType);

            // When a null value is encountered in the JSON we want to set a default value in the class.
            property.PropertyType = typeof(Nullable<>).MakeGenericType(new[] { property.PropertyType });
            property.ValueProvider = new NullToDefaultValueProvider(property.ValueProvider, defaultValue);

            // Remember that the underlying property is actually not nullable so GetValue() will never return null.
            // Thus the below just overrides JsonSerializerSettings.NullValueHandling to force the value to be set
            // (to the default) even when null is encountered.
            property.NullValueHandling = NullValueHandling.Include;
        }
        return property;
    }

    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    // See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
    static CustomContractResolver instance;

    // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
    static CustomContractResolver() { instance = new CustomContractResolver(); }

    public static CustomContractResolver Instance { get { return instance; } }

}

public abstract class ValueProviderDecorator : IValueProvider
{
    readonly IValueProvider baseProvider;

    public ValueProviderDecorator(IValueProvider baseProvider)
    {
        if (baseProvider == null)
            throw new ArgumentNullException();
        this.baseProvider = baseProvider;
    }

    public virtual object GetValue(object target) { return baseProvider.GetValue(target); }

    public virtual void SetValue(object target, object value) { baseProvider.SetValue(target, value); }
}

Примечания:

  • Преобразователь контракта работает путем изменения типасвойства, возвращающие ненулевые значения соответствующему типу Nullable<T>, а затем создание ValueProvider декоратора , который отображает входящее значение null на прежнее значение по умолчанию (которое не может бытьnull, так как базовый тип не может иметь значение null).

  • Нет необходимости переопределять JsonProperty.ShouldDeserialize.Этот предикат позволяет динамически игнорировать значение свойства JSON на основе состояния целевого объекта .Даже не передается десериализованное значение JSON.

  • Если в вашем типе используется параметризованный конструктор , вам также может потребоваться переопределить DefaultContractResolver.CreatePropertyFromConstructorParameter.

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

Рабочий пример. Net fiddle здесь .

...