如何使用 JSON.NET 序列化并忽略可空结构值

How to Serialize using JSON.NET and Ignore a Nullable Struct Value

我正在尝试使用 JSON.NET 和自定义 JsonConverter 来序列化可为 null 的结构。我希望 null 值在 JSON 输出中为 ignored/omitted 例如我希望下面的 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. to 中所述:

    A custom JsonConverter cannot prevent its value from being serialized, because the property name referring to it will already have been written out by the time the converter is invoked. In Json.NET's architecture it is the responsibility of the containing type to decide which of its properties to serialize; the value converter then decides how to serialize the value being written.

  2. NullValueHandling.Ignore 不起作用,因为 属性 Entity.Numbernot null,它有一个值,即具有 null 内部值 :

    的已分配 HasValue<int?> 结构
    Number = new HasValue<int?>(number) // Not Number = null
    
  3. 同样,DefaultValueHandling.Ignore 不起作用,因为 default(HasValue<int?>?) 具有与 null 可空值相同的值——如上所述,它与分配给 [= 的值不同21=].

那么你有什么选择?

您可以使用 conditional property serialization 来抑制 Number 的序列化,当它的值为 non-null 但内部值为空时:

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

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

演示 fiddle #1 here.

但是,这种设计似乎有点过于复杂——您有一个 nullable 包含一个结构,该结构封装了一个包含整数的 nullable——即 Nullable<HasValue<Nullable<int>>>。您真的需要两个级别的可空吗?如果没有,您可以简单地删除外部 Nullable<> 并且 DefaultValueHandling 现在 可以正常工作 :

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

演示 fiddle #2 here.

在这两种情况下,我将 NullJsonConverter 概括为处理 HasValue<T> 的所有可能类型 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 属性。
  • 添加 non-generic 接口以在序列化期间将值作为对象访问。
  • 直接反序列化内部值,然后在反序列化过程中调用参数化构造函数。

因此,如果您愿意,可以将 [JsonConverter(typeof(NullJsonConverter))] 应用于 HasValue<T> 本身。

你也可以考虑让你的 HasValue<T> 结构不可变,原因在 Why are mutable structs “evil”?.

中有解释