Как сериализовать с помощью JSON.NET и игнорировать Nullable Struct Value - PullRequest
1 голос
/ 22 мая 2019

Я пытаюсь сериализовать обнуляемую структуру, используя JSON.NET с пользовательским JsonConverter. Я хотел бы, чтобы значение null было проигнорировано / опущено в выводе JSON, например. Я хочу, чтобы мой вывод JSON ниже был {} вместо {"Number":null}. Как этого достичь? Вот минимальное повторение с модульным тестом, с которым я пытаюсь достичь.

[Fact]
public void UnitTest()
{
    int? number = null;
    var json = JsonConvert.SerializeObject(
        new Entity { Number = new HasValue<int?>(number) },
        new JsonSerializerSettings()
        { 
            DefaultValueHandling = DefaultValueHandling.Ignore,
            NullValueHandling = NullValueHandling.Ignore
        });

    Assert.Equal("{}", json); // Fails because json = {"Number":null}
}

public class Entity
{
    [JsonConverter(typeof(NullJsonConverter))]
    public HasValue<int?>? Number { get; set; }
}

public struct HasValue<T>
{
    public HasValue(T value) => this.Value = value;
    public object Value { get; set; }
}

public class NullJsonConverter : JsonConverter
{
    public override bool CanRead => false;
    public override bool CanConvert(Type objectType) => true;
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var values = (HasValue<int?>)value;
        var objectValue = values.Value;
        if (objectValue == null)
        {
            // How can I skip writing this property?
        }
        else
        {
            var token = JToken.FromObject(objectValue, serializer);
            token.WriteTo(writer);
        }
    }
}

1 Ответ

4 голосов
/ 23 мая 2019

У вас есть три проблемы:

  1. Как объяснено в этот ответ в Пользовательский преобразователь Json.NET не должен сериализовать свойство :

    A custom JsonConverter не может помешать сериализации его значения, так как ссылающееся на него имя свойства уже будет записано ко времениконвертер вызывается.В архитектуре Json.NET ответственность содержащего типа заключается в том, чтобы решить , какие его свойств следует сериализовать;Затем преобразователь значений решает как сериализовать записываемое значение.

  2. NullValueHandling.Ignore не работает, поскольку свойство Entity.Number равно не нуль , он имеет значение, а именно выделенную структуру HasValue<int?> с внутренним значением null :

    Number = new HasValue<int?>(number) // Not Number = null
    
  3. Аналогично DefaultValueHandling.Ignore не работает, потому что default(HasValue<int?>?) имеет то же значение, что и нулевое значение, допускающее значение NULL, которое, как упоминалось выше, отличается от значения, присвоенного Number.

Так что жеВаши варианты здесь?

Вы можете использовать условную сериализацию свойства , чтобы подавить сериализацию Number, когда его значение не является нулевым, но имеет нулевое внутреннее значение:

public class Entity
{
    [JsonConverter(typeof(NullJsonConverter))]
    public HasValue<int?>? Number { get; set; }

    public bool ShouldSerializeNumber() { return Number.HasValue && Number.Value.Value.HasValue; }
}

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

Однако этот дизайн выглядит немного слишком сложным - у вас есть nullable, содержащий структуру, которая инкапсулирует nullable, содержащий целое число- т.е. Nullable<HasValue<Nullable<int>>>.Вам действительно нужны оба уровня Nullable?Если нет, вы можете просто удалить внешние Nullable<> и DefaultValueHandling теперь будут просто работать :

public class Entity
{
    [JsonConverter(typeof(NullJsonConverter))]
    public HasValue<int?> Number { get; set; }
}

Демо-скрипта # 2 здесь .

В обоих случаях я обобщил NullJsonConverter для обработки всех возможных типов T для HasValue<T> следующим образом:

public struct HasValue<T> : IHasValue
{
    // Had to convert to c# 4.0 syntax for dotnetfiddle
    T m_value;
    public HasValue(T value) { this.m_value = value; }
    public T Value { get { return m_value; } set { m_value = value; } }

    public object GetValue() { return Value; }
}

internal interface IHasValue
{
    object GetValue();
}

public class NullJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) { throw new NotImplementedException(); }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var valueType = objectType.GetGenericArguments()[0];
        var valueValue = serializer.Deserialize(reader, valueType);
        return Activator.CreateInstance(objectType, valueValue);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, ((IHasValue)value).GetValue());
    }
}

В частности:

  • ИзменениеValue Свойство для ввода.
  • Добавление неуниверсального интерфейса для доступа к значению как объекту во время сериализации.
  • Непосредственная десериализация внутреннего значения с последующим вызовом параметризованного конструктора во время десериализации.

Таким образом, вы можете применить [JsonConverter(typeof(NullJsonConverter))] к HasValue<T>, если хотите.

Вы также можете рассмотреть возможность сделать структуру HasValue<T> неизменной по причинам, объясненным в Почему изменчивые структуры «злые»? .

...