【问题标题】:How to Serialize using JSON.NET and Ignore a Nullable Struct Value如何使用 JSON.NET 进行序列化并忽略可为空的结构值
【发布时间】:2019-05-22 16:09:21
【问题描述】:

我正在尝试使用带有自定义 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);
        }
    }
}

【问题讨论】:

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


    【解决方案1】:

    你这里有三个问题:

    1. 正如this answerCustom Json.NET converter should not serialize a property 中所述:

      custom JsonConverter 无法阻止其值被序列化,因为在调用转换器时引用它的属性名称已经被写出。在 Json.NET 的体系结构中,包含类型负责决定 哪些 属性要序列化;然后值转换器决定如何序列化正在写入的值。

    2. NullValueHandling.Ignore 不起作用,因为属性Entity.Numbernot null,它有一个值,即分配的HasValue&lt;int?&gt; 结构与null 内部值强>:

      Number = new HasValue<int?>(number) // Not Number = null
      
    3. 同样,DefaultValueHandling.Ignore 不起作用,因为 default(HasValue&lt;int?&gt;?) 具有与可为 null 的值相同的值 -- 如上所述,与分配给 Number 的值不同。

      李>

    那么你有什么选择呢?

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

    public class Entity
    {
        [JsonConverter(typeof(NullJsonConverter))]
        public HasValue<int?>? Number { get; set; }
    
        public bool ShouldSerializeNumber() { return Number.HasValue && Number.Value.Value.HasValue; }
    }
    

    演示小提琴 #1 here.

    然而,这种设计似乎有点过于复杂——你有一个包含一个结构的 nullable,它封装了一个包含整数的 nullable——即Nullable&lt;HasValue&lt;Nullable&lt;int&gt;&gt;&gt;。你真的需要两个可空的级别吗?如果没有,您可以简单地删除外部 Nullable&lt;&gt;DefaultValueHandling 现在正常工作

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

    演示小提琴 #2 here.

    在这两种情况下,我将NullJsonConverter 概括为处理THasValue&lt;T&gt; 的所有可能类型,如下所示:

    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&lt;T&gt; 本身。

    您也可以考虑让您的 HasValue&lt;T&gt; 结构不可变,原因在 Why are mutable structs “evil”? 中进行了解释。

    【讨论】:

      猜你喜欢
      • 2016-08-31
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      • 2015-11-15
      • 2016-08-27
      • 2020-11-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多