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

时间:2019-05-22 16:09:21

标签: c# .net json json.net jsonconverter

我正在尝试使用带有自定义JsonConverter的JSON.NET序列化可空结构。我想在JSON输出中忽略/忽略null值,例如我希望下面的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 个答案:

答案 0 :(得分:3)

您在这里遇到三个问题:

  1. this answer Custom Json.NET converter should not serialize a property 所述:

      

    custom JsonConverter不能阻止其值被序列化,因为引用该属性的属性名称将在调用转换器时被写出。在Json.NET的体系结构中,包含类型的责任是确定要序列化其属性的哪个。然后,值转换器决定如何对要写入的值进行序列化。

  2. third不起作用,因为属性NullValueHandling.Ignore 不为空,它具有一个值,即带有{的已分配Entity.Number结构{1}} 内在价值

    HasValue<int?>
  3. 类似地,null不起作用,因为Number = new HasValue<int?>(number) // Not Number = null 与可为空的可空值具有相同的值-如上所述,它与分配给DefaultValueHandling.Ignore的值不同。

那么您在这里有什么选择?

default(HasValue<int?>?)的值为非空但内部值为空的情况下,可以使用 conditional property serialization 禁止序列化Number

Number

演示小提琴#1 here

但是,这种设计似乎有些复杂-您有一个可空值,其中包含一个封装了包含整数的可空值的结构-即public class Entity { [JsonConverter(typeof(NullJsonConverter))] public HasValue<int?>? Number { get; set; } public bool ShouldSerializeNumber() { return Number.HasValue && Number.Value.Value.HasValue; } } 。您真的需要两个级别的可为空吗?如果没有,您只需移除外部Nullable<HasValue<Nullable<int>>>Nullable<>现在将正常工作

DefaultValueHandling

演示小提琴#2 here

在两种情况下,我都将public class Entity { [JsonConverter(typeof(NullJsonConverter))] public HasValue<int?> Number { get; set; } } 概括为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))]本身。

出于 Why are mutable structs “evil”? 中所述的原因,您可能还考虑将HasValue<T>结构设为不可变。