Подтвердить что ты не робот

Сериализация нулевого значения в JSON.NET

При сериализации произвольных данных через JSON.NET любое свойство, которое является null, записывается в JSON как

"propertyName": null

Это правильно, конечно.

Однако у меня есть требование автоматически перевести все нули в значение по умолчанию, равное по умолчанию. null string должен стать String.Empty, null int? должен стать 0, null bool? должен быть false и т.д.

NullValueHandling не помогает, так как я не хочу Ignore nulls, но я также не хочу Include их (Hmm, новая функция?).

Итак, я обратился к реализации пользовательского JsonConverter.
В то время как сама реализация была легкой, к сожалению, это все еще не сработало - CanConvert() никогда не вызывается для свойства с нулевым значением, поэтому WriteJson() также не вызывается. По-видимому, нули автоматически сериализуются непосредственно в null без настраиваемого конвейера.

Например, здесь приведен пример пользовательского конвертера для нулевых строк:

public class StringConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(string).IsAssignableFrom(objectType);
    }

    ...
    public override void WriteJson(JsonWriter writer, 
                object value, 
                JsonSerializer serializer)
    {
        string strValue = value as string;

        if (strValue == null)
        {
            writer.WriteValue(String.Empty);
        }
        else
        {
            writer.WriteValue(strValue);
        }
    }
}

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

Попадая в исходный код JSON.NET, я обнаружил, что (по-видимому, я не занимался большой глубиной) существует специальный регистр для проверки нулей и явный вызов .WriteNull().

Для чего это стоит, я попытался реализовать пользовательский JsonTextWriter и переопределить реализацию по умолчанию .WriteNull()...

public class NullJsonWriter : JsonTextWriter
{
    ... 
    public override void WriteNull()
    {
        this.WriteValue(String.Empty);
    }
}

Однако это не сработает, так как метод WriteNull() ничего не знает о базовом типе данных. Я уверен, что я могу вывести "" для любого нулевого значения, но это не работает хорошо, например. int, bool и т.д.

Итак, мой вопрос - не нужно ли преобразовывать всю структуру данных вручную, есть ли какое-либо решение или обходное решение для этого?

4b9b3361

Ответ 1

Хорошо, я думаю, что придумал решение (мое первое решение было не совсем правильным, но опять же я был в поезде). Вам необходимо создать специальный контрактный преобразователь и настраиваемый ValueProvider для типов Nullable. Рассмотрим это:

public class NullableValueProvider : IValueProvider
{
    private readonly object _defaultValue;
    private readonly IValueProvider _underlyingValueProvider;


    public NullableValueProvider(MemberInfo memberInfo, Type underlyingType)
    {
        _underlyingValueProvider = new DynamicValueProvider(memberInfo);
        _defaultValue = Activator.CreateInstance(underlyingType);
    }

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

    public object GetValue(object target)
    {
        return _underlyingValueProvider.GetValue(target) ?? _defaultValue;
    }
}

public class SpecialContractResolver : DefaultContractResolver
{
    protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
    {
        if(member.MemberType == MemberTypes.Property)
        {
            var pi = (PropertyInfo) member;
            if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>))
            {
                return new NullableValueProvider(member, pi.PropertyType.GetGenericArguments().First());
            }
        }
        else if(member.MemberType == MemberTypes.Field)
        {
            var fi = (FieldInfo) member;
            if(fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
                return new NullableValueProvider(member, fi.FieldType.GetGenericArguments().First());
        }

        return base.CreateMemberValueProvider(member);
    }
}

Затем я протестировал его, используя:

class Foo
{
    public int? Int { get; set; }
    public bool? Boolean { get; set; }
    public int? IntField;
}

И следующий случай:

[TestFixture]
public class Tests
{
    [Test]
    public void Test()
    {
        var foo = new Foo();

        var settings = new JsonSerializerSettings { ContractResolver = new SpecialContractResolver() };

        Assert.AreEqual(
            JsonConvert.SerializeObject(foo, Formatting.None, settings), 
            "{\"IntField\":0,\"Int\":0,\"Boolean\":false}");
    }
}

Надеюсь, это немного поможет...

Изменить – Лучшая идентификация типа a Nullable<>

Изменить – Добавлена ​​поддержка полей, а также свойств, а также поддержка копий поверх обычного DynamicValueProvider для выполнения большей части работы с обновленным тестом