Add logic for JsonIgnoreCondition[Never|WhenNull] on properties to win over global...
authorLayomi Akinrinade <laakinri@microsoft.com>
Mon, 13 Apr 2020 19:30:00 +0000 (15:30 -0400)
committerGitHub <noreply@github.com>
Mon, 13 Apr 2020 19:30:00 +0000 (12:30 -0700)
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTTypeToConvert.cs
src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs

index 330e305..2284869 100644 (file)
@@ -79,8 +79,9 @@ namespace System.Text.Json
 
         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);
             }
@@ -98,7 +99,8 @@ namespace System.Text.Json
                 propertyInfo,
                 parentClassType,
                 converter,
-                options);
+                options,
+                ignoreCondition);
         }
 
         internal static JsonPropertyInfo CreateProperty(
@@ -107,7 +109,8 @@ namespace System.Text.Json
             PropertyInfo? propertyInfo,
             Type parentClassType,
             JsonConverter converter,
-            JsonSerializerOptions options)
+            JsonSerializerOptions options,
+            JsonIgnoreCondition? ignoreCondition = null)
         {
             // Create the JsonPropertyInfo instance.
             JsonPropertyInfo jsonPropertyInfo = converter.CreateJsonPropertyInfo();
@@ -119,6 +122,7 @@ namespace System.Text.Json
                 runtimeClassType: converter.ClassType,
                 propertyInfo,
                 converter,
+                ignoreCondition,
                 options);
 
             return jsonPropertyInfo;
index 0a0d33a..549f93a 100644 (file)
@@ -92,12 +92,20 @@ namespace System.Text.Json
             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;
@@ -118,19 +126,16 @@ namespace System.Text.Json
             }
         }
 
-        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;
                 }
             }
@@ -152,11 +157,11 @@ namespace System.Text.Json
         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);
@@ -171,6 +176,7 @@ namespace System.Text.Json
             ClassType runtimeClassType,
             PropertyInfo? propertyInfo,
             JsonConverter converter,
+            JsonIgnoreCondition? ignoreCondition,
             JsonSerializerOptions options)
         {
             Debug.Assert(converter != null);
index 1ceb16d..5e5f219 100644 (file)
@@ -25,6 +25,7 @@ namespace System.Text.Json
             ClassType runtimeClassType,
             PropertyInfo? propertyInfo,
             JsonConverter converter,
+            JsonIgnoreCondition? ignoreCondition,
             JsonSerializerOptions options)
         {
             base.Initialize(
@@ -34,6 +35,7 @@ namespace System.Text.Json
                 runtimeClassType,
                 propertyInfo,
                 converter,
+                ignoreCondition,
                 options);
 
             if (propertyInfo != null)
@@ -57,7 +59,7 @@ namespace System.Text.Json
                 HasSetter = true;
             }
 
-            GetPolicies();
+            GetPolicies(ignoreCondition);
         }
 
         public override JsonConverter ConverterBase
index 4127189..800e211 100644 (file)
@@ -613,7 +613,7 @@ namespace System.Text.Json.Serialization.Tests
         {
             public int Int1 { get; set; }
             [JsonIgnore(Condition = JsonIgnoreCondition.WhenNull)]
-            public int MyInt { get; set;  }
+            public int MyInt { get; set; }
             public int Int2 { get; set; }
         }
 
@@ -734,7 +734,8 @@ namespace System.Text.Json.Serialization.Tests
         {
             string json = @"{""MyString"":""Random"",""MYSTRING"":null}";
 
-            var options = new JsonSerializerOptions {
+            var options = new JsonSerializerOptions
+            {
                 IgnoreNullValues = true,
                 PropertyNameCaseInsensitive = true
             };
@@ -807,5 +808,62 @@ namespace System.Text.Json.Serialization.Tests
             [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;
+        }
     }
 }