-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Closed
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Text.Json
Milestone
Description
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
- The serializer will throw on deserialization when a
nulltoken is applied to a value type, even ifIgnoreNullValuesis applied. There is an open issue to relax this behavior: IgnoreNullValues does not work with non-nullable value types #30795, after which those semantics can be applied per property. - An upcoming feature is being able to globally ignore "default values" on (de)serialization: System.Text.Json option to ignore default values during serialization #779. It would make sense to add a
WhenDefaultenum value toJsonIgnoreConditionwhen this is implemented.
Implementation
SidShetye, acid-chicken, mdmoura, wanton7, dgaspar and 28 moreMertsch
Metadata
Metadata
Assignees
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Text.Json