Skip to content

Commit

Permalink
Add logic for JsonIgnoreCondition[Never|WhenNull] on properties to wi…
Browse files Browse the repository at this point in the history
…n over global IgnoreReadOnlyValues (#34672)
  • Loading branch information
layomia authored Apr 13, 2020
1 parent c1566cc commit 6f080a4
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ public Dictionary<string, JsonParameterInfo> CreateParameterCache(int capacity,

public static JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
{
JsonIgnoreAttribute? ignoreAttribute = JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo);
if (ignoreAttribute?.Condition == JsonIgnoreCondition.Always)
JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo)?.Condition;

if (ignoreCondition == JsonIgnoreCondition.Always)
{
return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
}
Expand All @@ -98,7 +99,8 @@ public static JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo prope
propertyInfo,
parentClassType,
converter,
options);
options,
ignoreCondition);
}

internal static JsonPropertyInfo CreateProperty(
Expand All @@ -107,7 +109,8 @@ internal static JsonPropertyInfo CreateProperty(
PropertyInfo? propertyInfo,
Type parentClassType,
JsonConverter converter,
JsonSerializerOptions options)
JsonSerializerOptions options,
JsonIgnoreCondition? ignoreCondition = null)
{
// Create the JsonPropertyInfo instance.
JsonPropertyInfo jsonPropertyInfo = converter.CreateJsonPropertyInfo();
Expand All @@ -119,6 +122,7 @@ internal static JsonPropertyInfo CreateProperty(
runtimeClassType: converter.ClassType,
propertyInfo,
converter,
ignoreCondition,
options);

return jsonPropertyInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,20 @@ private void DeterminePropertyName()
PropertyNameKey = key;
}

private void DetermineSerializationCapabilities()
private void DetermineSerializationCapabilities(JsonIgnoreCondition? ignoreCondition)
{
if ((ClassType & (ClassType.Enumerable | ClassType.Dictionary)) == 0)
{
Debug.Assert(ignoreCondition != JsonIgnoreCondition.Always);

// Three possible values for ignoreCondition:
// null = JsonIgnore was not placed on this property, global IgnoreReadOnlyProperties wins
// WhenNull = only ignore when null, global IgnoreReadOnlyProperties loses
// Never = never ignore (always include), global IgnoreReadOnlyProperties loses
bool serializeReadOnlyProperty = ignoreCondition != null || !Options.IgnoreReadOnlyProperties;

// We serialize if there is a getter + not ignoring readonly properties.
ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties);
ShouldSerialize = HasGetter && (HasSetter || serializeReadOnlyProperty);

// We deserialize if there is a setter.
ShouldDeserialize = HasSetter;
Expand All @@ -118,19 +126,16 @@ private void DetermineSerializationCapabilities()
}
}

private void DetermineIgnoreCondition()
private void DetermineIgnoreCondition(JsonIgnoreCondition? ignoreCondition)
{
JsonIgnoreAttribute? ignoreAttribute;
if (PropertyInfo != null && (ignoreAttribute = GetAttribute<JsonIgnoreAttribute>(PropertyInfo)) != null)
if (ignoreCondition != null)
{
JsonIgnoreCondition condition = ignoreAttribute.Condition;

// We should have created a placeholder property for this upstream and shouldn't be down this code-path.
Debug.Assert(condition != JsonIgnoreCondition.Always);
Debug.Assert(PropertyInfo != null);
Debug.Assert(ignoreCondition != JsonIgnoreCondition.Always);

if (condition != JsonIgnoreCondition.Never)
if (ignoreCondition != JsonIgnoreCondition.Never)
{
Debug.Assert(condition == JsonIgnoreCondition.WhenNull);
Debug.Assert(ignoreCondition == JsonIgnoreCondition.WhenNull);
IgnoreNullValues = true;
}
}
Expand All @@ -152,11 +157,11 @@ private void DetermineIgnoreCondition()
public abstract bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf8JsonWriter writer);
public abstract bool GetMemberAndWriteJsonExtensionData(object obj, ref WriteStack state, Utf8JsonWriter writer);

public virtual void GetPolicies()
public virtual void GetPolicies(JsonIgnoreCondition? ignoreCondition)
{
DetermineSerializationCapabilities();
DetermineSerializationCapabilities(ignoreCondition);
DeterminePropertyName();
DetermineIgnoreCondition();
DetermineIgnoreCondition(ignoreCondition);
}

public abstract object? GetValueAsObject(object obj);
Expand All @@ -171,6 +176,7 @@ public virtual void Initialize(
ClassType runtimeClassType,
PropertyInfo? propertyInfo,
JsonConverter converter,
JsonIgnoreCondition? ignoreCondition,
JsonSerializerOptions options)
{
Debug.Assert(converter != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public override void Initialize(
ClassType runtimeClassType,
PropertyInfo? propertyInfo,
JsonConverter converter,
JsonIgnoreCondition? ignoreCondition,
JsonSerializerOptions options)
{
base.Initialize(
Expand All @@ -34,6 +35,7 @@ public override void Initialize(
runtimeClassType,
propertyInfo,
converter,
ignoreCondition,
options);

if (propertyInfo != null)
Expand All @@ -57,7 +59,7 @@ public override void Initialize(
HasSetter = true;
}

GetPolicies();
GetPolicies(ignoreCondition);
}

public override JsonConverter ConverterBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ private class ClassWithStructProperty_IgnoreConditionWhenNull
{
public int Int1 { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenNull)]
public int MyInt { get; set; }
public int MyInt { get; set; }
public int Int2 { get; set; }
}

Expand Down Expand Up @@ -734,7 +734,8 @@ public static void JsonIgnoreCondition_LastOneWins()
{
string json = @"{""MyString"":""Random"",""MYSTRING"":null}";

var options = new JsonSerializerOptions {
var options = new JsonSerializerOptions
{
IgnoreNullValues = true,
PropertyNameCaseInsensitive = true
};
Expand Down Expand Up @@ -807,5 +808,62 @@ public class ClassUsingIgnoreNeverAttribute
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string> { ["Key"] = "Value" };
}

[Fact]
public static void IgnoreConditionNever_WinsOver_IgnoreReadOnlyValues()
{
var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true };

// Baseline
string json = JsonSerializer.Serialize(new ClassWithReadOnlyString("Hello"), options);
Assert.Equal("{}", json);

// With condition to never ignore
json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreNever("Hello"), options);
Assert.Equal(@"{""MyString"":""Hello""}", json);

json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreNever(null), options);
Assert.Equal(@"{""MyString"":null}", json);
}

[Fact]
public static void IgnoreConditionWhenNull_WinsOver_IgnoreReadOnlyValues()
{
var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true };

// Baseline
string json = JsonSerializer.Serialize(new ClassWithReadOnlyString("Hello"), options);
Assert.Equal("{}", json);

// With condition to ignore when null
json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreWhenNull("Hello"), options);
Assert.Equal(@"{""MyString"":""Hello""}", json);

json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreWhenNull(null), options);
Assert.Equal(@"{}", json);
}

private class ClassWithReadOnlyString
{
public string MyString { get; }

public ClassWithReadOnlyString(string myString) => MyString = myString;
}

private class ClassWithReadOnlyString_IgnoreNever
{
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string MyString { get; }

public ClassWithReadOnlyString_IgnoreNever(string myString) => MyString = myString;
}

private class ClassWithReadOnlyString_IgnoreWhenNull
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenNull)]
public string MyString { get; }

public ClassWithReadOnlyString_IgnoreWhenNull(string myString) => MyString = myString;
}
}
}

0 comments on commit 6f080a4

Please sign in to comment.