Honor dictionary key policy when entry value is null (dotnet/corefx#42267)
authorLayomi Akinrinade <laakinri@microsoft.com>
Fri, 1 Nov 2019 02:27:18 +0000 (19:27 -0700)
committerGitHub <noreply@github.com>
Fri, 1 Nov 2019 02:27:18 +0000 (19:27 -0700)
* Honor dictionary key policy when entry value is null

* Address review feedback

* Remove reverse Json checks

Commit migrated from https://github.com/dotnet/corefx/commit/a24db0ba5666f46f50649810fe698ad267b60331

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.KeyPolicy.cs

index 98b88aaa4d85511970df81bb5c5fe6cababfff76..3605750fe5d38fa11ae5b6d8a36d8e2315d1fc2c 100644 (file)
@@ -5,6 +5,7 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Text.Json.Serialization;
 
 namespace System.Text.Json
 {
@@ -109,22 +110,26 @@ namespace System.Text.Json
 
             Debug.Assert(key != null);
 
+            if (Options.DictionaryKeyPolicy != null)
+            {
+                // We should not be in the Nullable-value implementation branch for extension data.
+                // (TValue should be typeof(object) or typeof(JsonElement)).
+                Debug.Assert(current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing);
+
+                key = Options.DictionaryKeyPolicy.ConvertName(key);
+
+                if (key == null)
+                {
+                    ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(Options.DictionaryKeyPolicy.GetType());
+                }
+            }
+
             if (value == null)
             {
                 writer.WriteNull(key);
             }
             else
             {
-                if (Options.DictionaryKeyPolicy != null)
-                {
-                    key = Options.DictionaryKeyPolicy.ConvertName(key);
-
-                    if (key == null)
-                    {
-                        ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(Options.DictionaryKeyPolicy.GetType());
-                    }
-                }
-
                 writer.WritePropertyName(key);
                 Converter.Write(writer, value.GetValueOrDefault(), Options);
             }
index d221a5d197c6b4a254c1ce40a44bb08d165b14e5..d39ea99a9873e84f178e44c388a84a5359671c0b 100644 (file)
@@ -145,23 +145,26 @@ namespace System.Text.Json
                     current.JsonPropertyInfo.PropertyInfo);
             }
 
+            Debug.Assert(key != null);
+
+            if (options.DictionaryKeyPolicy != null &&
+                // We do not convert extension data.
+                current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing)
+            {
+                key = options.DictionaryKeyPolicy.ConvertName(key);
+
+                if (key == null)
+                {
+                    ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(options.DictionaryKeyPolicy.GetType());
+                }
+            }
+
             if (value == null)
             {
                 writer.WriteNull(key);
             }
             else
             {
-                if (options.DictionaryKeyPolicy != null &&
-                    current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing) // We do not convert extension data.
-                {
-                    key = options.DictionaryKeyPolicy.ConvertName(key);
-
-                    if (key == null)
-                    {
-                        ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(options.DictionaryKeyPolicy.GetType());
-                    }
-                }
-
                 writer.WritePropertyName(key);
                 converter.Write(writer, value, options);
             }
index 4aaeb18768f5cd15729111178edc00f7661e3797..47f1595cc2a54955d1836879dbf8692421205df7 100644 (file)
@@ -46,6 +46,29 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(4, obj[1]["Key2"]);
         }
 
+        [Fact]
+        public static void IgnoreKeyPolicyForExtensionData()
+        {
+            var options = new JsonSerializerOptions
+            {
+                DictionaryKeyPolicy = JsonNamingPolicy.CamelCase // e.g. Key1 -> key1.
+            };
+
+            // Ensure we ignore key policy for extension data and deserialize keys as they are.
+            ClassWithExtensionData myClass = JsonSerializer.Deserialize<ClassWithExtensionData>(@"{""Key1"":1, ""Key2"":2}", options);
+            Assert.Equal(1, (myClass.ExtensionData["Key1"]).GetInt32());
+            Assert.Equal(2, (myClass.ExtensionData["Key2"]).GetInt32());
+
+            // Ensure we ignore key policy for extension data and serialize keys as they are.
+            Assert.Equal(@"{""Key1"":1,""Key2"":2}", JsonSerializer.Serialize(myClass, options));
+        }
+
+        public class ClassWithExtensionData
+        {
+            [JsonExtensionData]
+            public Dictionary<string, JsonElement> ExtensionData { get; set; }
+        }
+
         [Fact]
         public static void CamelCaseSerialize()
         {
@@ -72,6 +95,56 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(JsonCamel, json);
         }
 
+        [Fact]
+        public static void CamelCaseSerialize_Null_Values()
+        {
+            var options = new JsonSerializerOptions()
+            {
+                DictionaryKeyPolicy = JsonNamingPolicy.CamelCase // e.g. Key1 -> key1.
+            };
+
+            Dictionary<string, string>[] obj = new Dictionary<string, string>[]
+            {
+                new Dictionary<string, string>() { { "Key1", null }, { "Key2", null } },
+            };
+
+            const string Json = @"[{""Key1"":null,""Key2"":null}]";
+            const string JsonCamel = @"[{""key1"":null,""key2"":null}]";
+
+            // Without key policy option, serialize keys as they are.
+            string json = JsonSerializer.Serialize<object>(obj);
+            Assert.Equal(Json, json);
+
+            // With key policy option, serialize keys with camel casing.
+            json = JsonSerializer.Serialize<object>(obj, options);
+            Assert.Equal(JsonCamel, json);
+        }
+
+        [Fact]
+        public static void CamelCaseSerialize_Null_Nullable_Values()
+        {
+            var options = new JsonSerializerOptions()
+            {
+                DictionaryKeyPolicy = JsonNamingPolicy.CamelCase // e.g. Key1 -> key1.
+            };
+
+            Dictionary<string, int?>[] obj = new Dictionary<string, int?>[]
+            {
+                new Dictionary<string, int?>() { { "Key1", null }, { "Key2", null } },
+            };
+
+            const string Json = @"[{""Key1"":null,""Key2"":null}]";
+            const string JsonCamel = @"[{""key1"":null,""key2"":null}]";
+
+            // Without key policy option, serialize keys as they are.
+            string json = JsonSerializer.Serialize<object>(obj);
+            Assert.Equal(Json, json);
+
+            // With key policy option, serialize keys with camel casing.
+            json = JsonSerializer.Serialize<object>(obj, options);
+            Assert.Equal(JsonCamel, json);
+        }
+
         [Fact]
         public static void CustomNameDeserialize()
         {