Fix issue causing subsequent properties to be skipped during deserialization once...
authorMikel Blanchard <mblanchard@macrosssoftware.com>
Mon, 26 Aug 2019 21:04:49 +0000 (14:04 -0700)
committerSteve Harter <steveharter@users.noreply.github.com>
Mon, 26 Aug 2019 21:04:49 +0000 (16:04 -0500)
Commit migrated from https://github.com/dotnet/corefx/commit/fb30a2d1a2bffbba2c039fd294c471962576a2d7

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs

index 287242e..5bdf3cf 100644 (file)
@@ -82,6 +82,7 @@ namespace System.Text.Json
                         if (readStack.Current.Drain)
                         {
                             readStack.Pop();
+                            readStack.Current.EndObject();
                         }
                         else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible)
                         {
index 3daa7c1..ae65ffd 100644 (file)
@@ -126,6 +126,7 @@ namespace System.Text.Json
             PropertyIndex = 0;
             EndProperty();
         }
+
         public void EndProperty()
         {
             CollectionPropertyInitialized = false;
index 4a2f2ca..55bcecf 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Linq;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -427,7 +428,7 @@ namespace System.Text.Json.Serialization.Tests
 
         public class ClassWithPopulatedListAndSetter
         {
-            public List<int> MyList { get; set;  } = new List<int>() { 1 };
+            public List<int> MyList { get; set; } = new List<int>() { 1 };
         }
 
         [Fact]
@@ -438,5 +439,40 @@ namespace System.Text.Json.Serialization.Tests
             ClassWithPopulatedListAndSetter obj = JsonSerializer.Deserialize<ClassWithPopulatedListAndSetter>(json);
             Assert.Equal(2, obj.MyList.Count);
         }
+
+        public class ClassWithMixedSetters
+        {
+            public List<int> SkippedChild { get; }
+            public List<int> ParsedChild { get; set; }
+            public IEnumerable<int> AnotherSkippedChild { get; }
+            public IEnumerable<int> AnotherParsedChild { get; set; }
+        }
+
+        [Fact]
+        public static void ClassWithMixedSettersIsParsed()
+        {
+            // Tests that the parser picks back up after skipping/draining ignored elements.
+            string json = @"{
+                ""SkippedChild"": {},
+                ""ParsedChild"": [18],
+                ""UnmatchedProp"": null,
+                ""AnotherSkippedChild"": [{""DrainProp1"":{}, ""DrainProp2"":{""SubProp"":0}}],
+                ""AnotherSkippedChild"": {},
+                ""AnotherParsedChild"": [18,20]
+            }";
+
+            ClassWithMixedSetters parsedObject = JsonSerializer.Deserialize<ClassWithMixedSetters>(json);
+
+            Assert.Null(parsedObject.SkippedChild);
+
+            Assert.NotNull(parsedObject.ParsedChild);
+            Assert.Equal(1, parsedObject.ParsedChild.Count);
+            Assert.Equal(18, parsedObject.ParsedChild[0]);
+
+            Assert.Null(parsedObject.AnotherSkippedChild);
+
+            Assert.NotNull(parsedObject.AnotherParsedChild);
+            Assert.True(parsedObject.AnotherParsedChild.SequenceEqual(new int[] { 18, 20 }));
+        }
     }
 }
index a389db2..d653c6d 100644 (file)
@@ -922,7 +922,7 @@ namespace System.Text.Json.Serialization.Tests
         public static void ClassWithNoSetterAndDictionary()
         {
             // We don't attempt to deserialize into dictionaries without a setter.
-            string json = @"{""MyDictionary"":{""Key1"":""Value1"", ""Key2"":""Value2""}}";
+            string json = @"{""MyDictionary"":{""Key1"":""Value1"", ""Key2"":""Value2""},""MyDictionaryWithSetter"":{""Key1"":""Value1""}}";
             ClassWithPopulatedDictionaryAndNoSetter obj = JsonSerializer.Deserialize<ClassWithPopulatedDictionaryAndNoSetter>(json);
             Assert.Equal(1, obj.MyDictionary.Count);
         }
@@ -936,6 +936,43 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(1, obj.MyImmutableDictionary.Count);
         }
 
+        public class ClassWithNoSetter
+        {
+            public Dictionary<string, int> SkippedChild { get; }
+            public Dictionary<string, int> ParsedChild { get; set; }
+            public IDictionary<string, int> AnotherSkippedChild { get; }
+            public IDictionary<string, int> AnotherParsedChild { get; set; }
+        }
+
+        [Fact]
+        public static void ClassWithNoSetterAndValidProperty()
+        {
+            // Tests that the parser picks back up after skipping/draining ignored elements.
+            string json = @"{
+                ""SkippedChild"": {},
+                ""ParsedChild"": {""Key1"":18},
+                ""UnmatchedProp"": null,
+                ""AnotherSkippedChild"": {""DrainProp1"":{}, ""DrainProp2"":{""SubProp"":0}},
+                ""AnotherSkippedChild"": {},
+                ""AnotherParsedChild"": {""Key1"":18, ""Key2"":20}
+            }";
+
+            ClassWithNoSetter parsedObject = JsonSerializer.Deserialize<ClassWithNoSetter>(json);
+
+            Assert.Null(parsedObject.SkippedChild);
+
+            Assert.NotNull(parsedObject.ParsedChild);
+            Assert.Equal(1, parsedObject.ParsedChild.Count);
+            Assert.Equal(18, parsedObject.ParsedChild["Key1"]);
+
+            Assert.Null(parsedObject.AnotherSkippedChild);
+
+            Assert.NotNull(parsedObject.AnotherParsedChild);
+            Assert.Equal(2, parsedObject.AnotherParsedChild.Count);
+            Assert.Equal(18, parsedObject.AnotherParsedChild["Key1"]);
+            Assert.Equal(20, parsedObject.AnotherParsedChild["Key2"]);
+        }
+
         public class ClassWithPopulatedDictionaryAndSetter
         {
             public ClassWithPopulatedDictionaryAndSetter()
index 3c82ad2..eb6f11d 100644 (file)
@@ -356,5 +356,114 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(1, parsedObject.MySimpleTestClass.MyInt32Array[0]);
             Assert.Equal(2, parsedObject.MyInt32Array[0]);
         }
+
+        public class ClassWithNoSetter
+        {
+            public SimpleTestClass SkippedChild { get; }
+            public SimpleTestClass ParsedChild { get; set; }
+            public SimpleTestClass AnotherSkippedChild { get; }
+            public SimpleTestClass AnotherParsedChild { get; set; }
+        }
+
+        [Fact]
+        public static void ClassWithNoSetterAndValidProperty()
+        {
+            // Tests that the parser picks back up after skipping/draining ignored elements.
+            string json = @"{
+                ""SkippedChild"": {},
+                ""ParsedChild"": {""MyInt16"":18},
+                ""UnmatchedProp"": null,
+                ""AnotherSkippedChild"": {""DrainProp1"":{}, ""DrainProp2"":{""SubProp"":0}},
+                ""AnotherSkippedChild"": {},
+                ""AnotherParsedChild"": {""MyInt16"":20}
+            }";
+
+            ClassWithNoSetter parsedObject = JsonSerializer.Deserialize<ClassWithNoSetter>(json);
+
+            Assert.Null(parsedObject.SkippedChild);
+
+            Assert.NotNull(parsedObject.ParsedChild);
+            Assert.Equal(18, parsedObject.ParsedChild.MyInt16);
+
+            Assert.Null(parsedObject.AnotherSkippedChild);
+
+            Assert.NotNull(parsedObject.AnotherParsedChild);
+            Assert.Equal(20, parsedObject.AnotherParsedChild.MyInt16);
+        }
+
+        public class ClassMixingSkippedTypes
+        {
+            public IDictionary<string, string> SkippedDictionary { get; }
+
+            public SimpleTestClass ParsedClass { get; set; }
+
+            public IList<int> SkippedList { get; }
+            public IDictionary<string, string> AnotherSkippedDictionary { get; }
+            public SimpleTestClass SkippedClass { get; }
+
+            public Dictionary<string, int> ParsedDictionary { get; set; }
+
+            public IList<int> AnotherSkippedList { get; }
+            public IDictionary<string, string> AnotherSkippedDictionary2 { get; }           
+            public IDictionary<string, string> SkippedDictionaryNotInJson { get; }
+            public SimpleTestClass AnotherSkippedClass { get; }
+
+            public int[] ParsedList { get; set; }
+
+            public ClassMixingSkippedTypes ParsedSubMixedTypeParsedClass { get; set; }
+        }
+
+        [Fact]
+        public static void ClassWithMixingSkippedTypes()
+        {
+            // Tests that the parser picks back up after skipping/draining ignored elements. Complex version.
+            string json = @"{
+                ""SkippedDictionary"": {},
+                ""ParsedClass"": {""MyInt16"":18},
+                ""SkippedList"": [18,20],
+                ""UnmatchedList"": [{},{}],
+                ""AnotherSkippedDictionary"": {""Key"":""Value""},
+                ""SkippedClass"": {""MyInt16"":99},
+                ""ParsedDictionary"": {""Key1"":18},
+                ""UnmatchedProp"": null,
+                ""AnotherSkippedList"": null,
+                ""AnotherSkippedDictionary2"": {""Key"":""Value""},
+                ""AnotherSkippedDictionary2"": {""Key"":""Dupe""},
+                ""AnotherSkippedClass"": {},
+                ""ParsedList"": [18,20],
+                ""ParsedSubMixedTypeParsedClass"": {""ParsedDictionary"": {""Key1"":18}},
+                ""UnmatchedDictionary"": {""DrainProp1"":{}, ""DrainProp2"":{""SubProp"":0}}                
+            }";
+
+            ClassMixingSkippedTypes parsedObject = JsonSerializer.Deserialize<ClassMixingSkippedTypes>(json);
+
+            Assert.Null(parsedObject.SkippedDictionary);
+
+            Assert.NotNull(parsedObject.ParsedClass);
+            Assert.Equal(18, parsedObject.ParsedClass.MyInt16);
+
+            Assert.Null(parsedObject.SkippedList);
+            Assert.Null(parsedObject.AnotherSkippedDictionary);
+            Assert.Null(parsedObject.SkippedClass);
+
+            Assert.NotNull(parsedObject.ParsedDictionary);
+            Assert.Equal(1, parsedObject.ParsedDictionary.Count);
+            Assert.Equal(18, parsedObject.ParsedDictionary["Key1"]);
+
+            Assert.Null(parsedObject.AnotherSkippedList);
+            Assert.Null(parsedObject.AnotherSkippedDictionary2);
+            Assert.Null(parsedObject.SkippedDictionaryNotInJson);
+            Assert.Null(parsedObject.AnotherSkippedClass);
+
+            Assert.NotNull(parsedObject.ParsedList);
+            Assert.Equal(2, parsedObject.ParsedList.Length);
+            Assert.Equal(18, parsedObject.ParsedList[0]);
+            Assert.Equal(20, parsedObject.ParsedList[1]);
+
+            Assert.NotNull(parsedObject.ParsedSubMixedTypeParsedClass);
+            Assert.NotNull(parsedObject.ParsedSubMixedTypeParsedClass.ParsedDictionary);
+            Assert.Equal(1, parsedObject.ParsedSubMixedTypeParsedClass.ParsedDictionary.Count);
+            Assert.Equal(18, parsedObject.ParsedSubMixedTypeParsedClass.ParsedDictionary["Key1"]);
+        }
     }
 }