[3.0 System.Text.Json]JsonExtensionData should allow non-JsonElement types during...
authorMarco Rossignoli <marco.rossignoli@gmail.com>
Wed, 12 Jun 2019 15:08:09 +0000 (17:08 +0200)
committerAhson Khan <ahkha@microsoft.com>
Wed, 12 Jun 2019 15:08:09 +0000 (08:08 -0700)
* fix serialization

* serialize all as dictionary

* address PR feedback

* address PR feedback

* remove redundant test

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

src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs

index ab7ec94..9e06826 100644 (file)
   <data name="SerializationNotSupportedCollectionType" xml:space="preserve">
     <value>The collection type '{0}' is not supported.</value>
   </data>
-  <data name="SerializationDataExtensionPropertyInvalidElement" xml:space="preserve">
-    <value>The data extension property '{0}.{1}' cannot contain dictionary values of type '{2}'. Dictionary values must be of type JsonElement.</value>
-  </data>
   <data name="SerializationNotSupportedCollection" xml:space="preserve">
     <value>The collection type '{0}' on '{1}' is not supported.</value>
   </data>
index 94cbae9..a7ee061 100644 (file)
@@ -27,7 +27,7 @@ namespace System.Text.Json
                 if (enumerable == null)
                 {
                     // Write a null object or enumerable.
-                    state.Current.WriteObjectOrArrayStart(ClassType.Dictionary, writer, writeNull : true);
+                    state.Current.WriteObjectOrArrayStart(ClassType.Dictionary, writer, writeNull: true);
                     return true;
                 }
 
@@ -37,36 +37,28 @@ namespace System.Text.Json
 
             if (state.Current.Enumerator.MoveNext())
             {
-                // Handle DataExtension.
-                if (ReferenceEquals(jsonPropertyInfo, state.Current.JsonClassInfo.DataExtensionProperty))
+                // Check for polymorphism.
+                if (elementClassInfo.ClassType == ClassType.Unknown)
                 {
-                    WriteExtensionData(writer, ref state.Current);
+                    object currentValue = ((IDictionaryEnumerator)state.Current.Enumerator).Entry.Value;
+                    GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
+                }
+
+                if (elementClassInfo.ClassType == ClassType.Value)
+                {
+                    elementClassInfo.GetPolicyProperty().WriteDictionary(ref state.Current, writer);
+                }
+                else if (state.Current.Enumerator.Current == null)
+                {
+                    writer.WriteNull(jsonPropertyInfo.Name);
                 }
                 else
                 {
-                    // Check for polymorphism.
-                    if (elementClassInfo.ClassType == ClassType.Unknown)
-                    {
-                        object currentValue = ((IDictionaryEnumerator)state.Current.Enumerator).Entry.Value;
-                        GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
-                    }
-
-                    if (elementClassInfo.ClassType == ClassType.Value)
-                    {
-                        elementClassInfo.GetPolicyProperty().WriteDictionary(ref state.Current, writer);
-                    }
-                    else if (state.Current.Enumerator.Current == null)
-                    {
-                        writer.WriteNull(jsonPropertyInfo.Name);
-                    }
-                    else
-                    {
-                        // An object or another enumerator requires a new stack frame.
-                        var enumerator = (IDictionaryEnumerator)state.Current.Enumerator;
-                        object value = enumerator.Value;
-                        state.Push(elementClassInfo, value);
-                        state.Current.KeyName = (string)enumerator.Key;
-                    }
+                    // An object or another enumerator requires a new stack frame.
+                    var enumerator = (IDictionaryEnumerator)state.Current.Enumerator;
+                    object value = enumerator.Value;
+                    state.Push(elementClassInfo, value);
+                    state.Current.KeyName = (string)enumerator.Key;
                 }
 
                 return false;
@@ -134,21 +126,5 @@ namespace System.Text.Json
                 converter.Write(escapedKey, value, writer);
             }
         }
-
-        private static void WriteExtensionData(Utf8JsonWriter writer, ref WriteStackFrame frame)
-        {
-            DictionaryEntry entry = ((IDictionaryEnumerator)frame.Enumerator).Entry;
-            if (entry.Value is JsonElement element)
-            {
-                Debug.Assert(entry.Key is string);
-
-                string propertyName = (string)entry.Key;
-                element.WriteProperty(propertyName, writer);
-            }
-            else
-            {
-                ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(frame.JsonClassInfo, entry.Value.GetType());
-            }
-        }
     }
 }
index c4f7f69..bd505b5 100644 (file)
@@ -118,12 +118,6 @@ namespace System.Text.Json
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(JsonClassInfo jsonClassInfo, Type invalidType)
-        {
-            throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalidElement, jsonClassInfo.Type, jsonClassInfo.DataExtensionProperty.PropertyInfo.Name, invalidType));
-        }
-
-        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowInvalidOperationException_DeserializeMissingParameterlessConstructor(Type invalidType)
         {
             throw new NotSupportedException(SR.Format(SR.DeserializeMissingParameterlessConstructor, invalidType));
index c45ed4e..b28af0e 100644 (file)
@@ -178,23 +178,63 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-        public static void InvalidExtensionValue()
+        public static void ExtensionPropertyObjectValue_Empty()
+        {
+            // Baseline
+            ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Parse<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+            Assert.Equal(@"{""MyOverflow"":{}}", JsonSerializer.ToString(obj));
+        }
+
+        [Fact]
+        public static void ExtensionPropertyObjectValue()
         {
             // Baseline
             ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Parse<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
             obj.MyOverflow.Add("test", new object());
+            obj.MyOverflow.Add("test1", 1);
 
-            try
-            {
-                JsonSerializer.ToString(obj);
-                Assert.True(false, "InvalidOperationException should have thrown.");
-            }
-            catch (InvalidOperationException e)
-            {
-                // Verify the exception contains the property name and invalid type.
-                Assert.Contains("ClassWithExtensionPropertyAlreadyInstantiated.MyOverflow", e.Message);
-                Assert.Contains("System.Object", e.Message);
-            }
+            Assert.Equal(@"{""MyOverflow"":{""test"":{},""test1"":1}}", JsonSerializer.ToString(obj));
+        }
+
+        [Fact]
+        public static void ExtensionPropertyObjectValue_RoundTrip()
+        {
+            // Baseline
+            ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Parse<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+            obj.MyOverflow.Add("test", new object());
+            obj.MyOverflow.Add("test1", 1);
+            obj.MyOverflow.Add("test2", "text");
+            obj.MyOverflow.Add("test3", new DummyObj() { Prop = "ObjectProp" });
+            obj.MyOverflow.Add("test4", new DummyStruct() { Prop = "StructProp" });
+            obj.MyOverflow.Add("test5", new Dictionary<string, object>() { { "Key", "Value" }, { "Key1", "Value1" }, });
+
+            ClassWithExtensionPropertyAlreadyInstantiated roundTripObj = JsonSerializer.Parse<ClassWithExtensionPropertyAlreadyInstantiated>(JsonSerializer.ToString(obj));
+
+            Assert.Equal(6, roundTripObj.MyOverflow.Count);
+
+            Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test"]);
+            Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test1"]);
+            Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test2"]);
+            Assert.IsType<JsonElement>(roundTripObj.MyOverflow["test3"]);
+
+            Assert.Equal(JsonValueType.Object, ((JsonElement)roundTripObj.MyOverflow["test"]).Type);
+
+            Assert.Equal(JsonValueType.Number, ((JsonElement)roundTripObj.MyOverflow["test1"]).Type);
+            Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt32());
+            Assert.Equal(1, ((JsonElement)roundTripObj.MyOverflow["test1"]).GetInt64());
+
+            Assert.Equal(JsonValueType.String, ((JsonElement)roundTripObj.MyOverflow["test2"]).Type);
+            Assert.Equal("text", ((JsonElement)roundTripObj.MyOverflow["test2"]).GetString());
+
+            Assert.Equal(JsonValueType.Object, ((JsonElement)roundTripObj.MyOverflow["test3"]).Type);
+            Assert.Equal("ObjectProp", ((JsonElement)roundTripObj.MyOverflow["test3"]).GetProperty("Prop").GetString());
+
+            Assert.Equal(JsonValueType.Object, ((JsonElement)roundTripObj.MyOverflow["test4"]).Type);
+            Assert.Equal("StructProp", ((JsonElement)roundTripObj.MyOverflow["test4"]).GetProperty("Prop").GetString());
+
+            Assert.Equal(JsonValueType.Object, ((JsonElement)roundTripObj.MyOverflow["test5"]).Type);
+            Assert.Equal("Value", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key").GetString());
+            Assert.Equal("Value1", ((JsonElement)roundTripObj.MyOverflow["test5"]).GetProperty("Key1").GetString());
         }
 
         [Fact]
@@ -214,6 +254,26 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(3, child.MyOverflow["MyIntMissingChild"].GetInt32());
         }
 
+        [Fact]
+        public static void ExtensionProperty_InvalidDictionary()
+        {
+            ClassWithInvalidExtensionPropertyStringString obj1 = new ClassWithInvalidExtensionPropertyStringString();
+            Assert.Throws<InvalidOperationException>(() => JsonSerializer.ToString(obj1));
+
+            ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString();
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.ToString(obj2));
+        }
+
+        public class DummyObj
+        {
+            public string Prop { get; set; }
+        }
+
+        public struct DummyStruct
+        {
+            public string Prop { get; set; }
+        }
+
         public class ClassWithExtensionPropertyAlreadyInstantiated
         {
             public ClassWithExtensionPropertyAlreadyInstantiated()
@@ -246,6 +306,18 @@ namespace System.Text.Json.Serialization.Tests
             public Dictionary<string, int> MyOverflow { get; set; }
         }
 
+        public class ClassWithInvalidExtensionPropertyStringString
+        {
+            [JsonExtensionData]
+            public Dictionary<string, string> MyOverflow { get; set; }
+        }
+
+        public class ClassWithInvalidExtensionPropertyObjectString
+        {
+            [JsonExtensionData]
+            public Dictionary<DummyObj, string> MyOverflow { get; set; }
+        }
+
         public class ClassWithTwoExtensionPropertys
         {
             [JsonExtensionData]