3

我正在尝试使用 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);
        }
    }
}
4

1 回答 1

6

您在这里遇到三个问题:

  1. 正如对Custom Json.NET 转换器的答案中所解释的那样,不应序列化属性

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

  2. NullValueHandling.Ignore不起作用,因为该属性Entity.Number不是null ,它有一个值,即具有内部值的分配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<HasValue<Nullable<int>>>. 你真的需要两个可空的级别吗?如果没有,您可以简单地删除外部Nullable<>DefaultValueHandling现在就可以工作了

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

演示小提琴#2在这里

在这两种情况下,我都概括NullJsonConverter为处理所有可能的类型THasValue<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>结构不可变,原因在为什么可变结构是“邪恶的”?.

于 2019-05-22T22:00:01.977 回答