Skip to content

System.Text.Json ignore only when null API enhancement #30687

@CodeBlanch

Description

@CodeBlanch

Rationale

Right now null handling is an all or nothing thing. We have an option to ignore all null values (JsonSerializerOptions.IgnoreNullValues) and we have an option to ignore properties completely (JsonIgnoreAttribute), but you can't specify per-property handling. Per-property handling is useful when you want to hide something from JSON unless it is populated, perhaps because it was not requested or is only applicable to certain audiences.

This gives us parity with something like Json.NET's NullValueHandling.

Proposed API

Via expansion of JsonIgnoreAttribute...

namespace System.Text.Json.Serialization
{
  /// <summary>
  /// Controls how the <see cref="JsonIgnoreAttribute"/> will decide to ignore properties.
  /// </summary>
  public enum JsonIgnoreCondition
  {
      /// <summary>
      /// Property will always be ignored.
      /// </summary>
      Always,
      /// <summary>
      /// Property will only be ignored if it is null.
      /// </summary>
      WhenNull,
      /// <summary>
      /// Property will always be serialized/deserialized regardless of <see cref="JsonSerializerOptions"/> configuration.
      /// </summary>
      Never
  }

  /// <summary>
  /// Prevents a property from being serialized or deserialized.
  /// </summary>
  [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  public sealed class JsonIgnoreAttribute : JsonAttribute
  {
      /// <summary>
      /// Specifies the condition that must be met before a property will be ignored. Default value: <see cref="JsonIgnoreCondition.Always"/>.
      /// </summary>
      public JsonIgnoreCondition Condition { get; set; } = JsonIgnoreCondition.Always;

      /// <summary>
      /// Initializes a new instance of <see cref="JsonIgnoreAttribute"/>.
      /// </summary>
      public JsonIgnoreAttribute() { }
  }
}

Usage

Always

This is equivalent to the current [JsonIgnore] semantics where properties that have this value are always ignored.

WhenNull

public class ClassUsingIgnoreWhenNullAttribute
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenNull)]
    public SimpleTestClass Class { get; set; }

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenNull)]
    public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string> { ["Key"] = "Value" };
}

[Fact]
public static void SerializerSupportsClassUsingIgnoreWhenNullAttribute()
{
    string json = @"{""Class"":{""MyInt16"":18}, ""Dictionary"":null}";

    ClassUsingIgnoreWhenNullAttribute obj = JsonSerializer.Deserialize<ClassUsingIgnoreWhenNullAttribute>(json);

    // Class is deserialized because it is not null in json...
    Assert.NotNull(obj.Class);
    Assert.Equal(18, obj.Class.MyInt16);

    // Dictionary is left alone because it is null in json...
    Assert.NotNull(obj.Dictionary);
    Assert.Equal(1, obj.Dictionary.Count);
    Assert.Equal("Value", obj.Dictionary["Key"]);

    obj = new ClassUsingIgnoreWhenNullAttribute();

    json = JsonSerializer.Serialize(obj);

    // Class is not included in json because it was null, Dictionary is included because it is not null...
    Assert.Equal(@"{""Dictionary"":{""Key"":""Value""}}", json);
}

Never

public class ClassUsingIgnoreWhenNullAttribute
{
    public SimpleTestClass Class { get; set; } new MySimpleTestClass { MyInt16 = 18 };

    [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
    public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string> { ["Key"] = "Value" };
}

[Fact]
public static void SerializerSupportsClassUsingIgnoreNeverAttribute()
{
    string json = @"{""Class"":null, ""Dictionary"":null}";
    var options = new JsonSerializerOptions { IgnoreNullValues = true };

    ClassUsingIgnoreWhenNullAttribute obj = JsonSerializer.Deserialize<ClassUsingIgnoreWhenNullAttribute>(json, options);

    // Class is not deserialized because it is null in json...
    Assert.NotNull(obj.Class);
    Assert.Equal(18, obj.Class.MyInt16);

    // Dictionary is deserialized regardless of being null in json...
    Assert.Null(obj.Dictionary);

    obj = new ClassUsingIgnoreWhenNullAttribute();
    obj.Class = null;

    json = JsonSerializer.Serialize(obj, options);

    // Class is not included in json because it was null, Dictionary is included regardless of being null...
    Assert.Equal(@"{""Dictionary"":null}", json);
}

Considerations

Implementation

See dotnet/corefx#40522

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions