Avoid StackOverflowException when cyclic enumerable Type (dotnet/corefx#37818)
authorSteve Harter <steveharter@users.noreply.github.com>
Tue, 21 May 2019 18:50:53 +0000 (11:50 -0700)
committerGitHub <noreply@github.com>
Tue, 21 May 2019 18:50:53 +0000 (11:50 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/857d1d1a9e1cd3597e3b1488f119d3a52e8b921a

14 files changed:
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs

index 6160df0..07936ee 100644 (file)
@@ -20,6 +20,11 @@ namespace System.Text.Json.Serialization
         private static readonly JsonEnumerableConverter s_jsonIEnumerableConstuctibleConverter = new DefaultIEnumerableConstructibleConverter();
         private static readonly JsonEnumerableConverter s_jsonImmutableConverter = new DefaultImmutableConverter();
 
+        private JsonClassInfo _runtimeClassInfo;
+
+        private Type _elementType;
+        private JsonClassInfo _elementClassInfo;
+
         public static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable<object, object, object>();
 
         public ClassType ClassType;
@@ -43,8 +48,38 @@ namespace System.Text.Json.Serialization
         public bool IsPropertyPolicy {get; protected set;}
         public bool IgnoreNullValues { get; private set; }
 
-        // todo: to minimize hashtable lookups, cache JsonClassInfo:
-        //public JsonClassInfo ClassInfo;
+        // Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions.
+        protected JsonSerializerOptions Options { get; set; }
+
+        public JsonClassInfo RuntimeClassInfo
+        {
+            get
+            {
+                if (_runtimeClassInfo == null)
+                {
+                    _runtimeClassInfo = Options.GetOrAddClass(RuntimePropertyType);
+                }
+
+                return _runtimeClassInfo;
+            }
+        }
+
+        /// <summary>
+        /// Return the JsonClassInfo for the element type, or null if the the property is not an enumerable or dictionary.
+        /// </summary>
+        public JsonClassInfo ElementClassInfo
+        {
+            get
+            {
+                if (_elementClassInfo == null && _elementType != null)
+                {
+                    Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary);
+                    _elementClassInfo = Options.GetOrAddClass(_elementType);
+                }
+
+                return _elementClassInfo;
+            }
+        }
 
         public virtual void Initialize(
             Type parentClassType,
@@ -59,18 +94,13 @@ namespace System.Text.Json.Serialization
             RuntimePropertyType = runtimePropertyType;
             PropertyInfo = propertyInfo;
             ClassType = JsonClassInfo.GetClassType(runtimePropertyType);
-            if (elementType != null)
-            {
-                Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary);
-                ElementClassInfo = options.GetOrAddClass(elementType);
-            }
-
+            _elementType = elementType;
+            Options = options;
             IsNullableType = runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
             CanBeNull = IsNullableType || !runtimePropertyType.IsValueType;
         }
 
         public bool CanBeNull { get; private set; }
-        public JsonClassInfo ElementClassInfo { get; private set; }
         public JsonEnumerableConverter EnumerableConverter { get; private set; }
 
         public bool IsNullableType { get; private set; }
@@ -83,14 +113,14 @@ namespace System.Text.Json.Serialization
 
         public Type RuntimePropertyType { get; private set; }
 
-        public virtual void GetPolicies(JsonSerializerOptions options)
+        public virtual void GetPolicies()
         {
-            DetermineSerializationCapabilities(options);
-            DeterminePropertyName(options);
-            IgnoreNullValues = options.IgnoreNullValues;
+            DetermineSerializationCapabilities();
+            DeterminePropertyName();
+            IgnoreNullValues = Options.IgnoreNullValues;
         }
 
-        private void DeterminePropertyName(JsonSerializerOptions options)
+        private void DeterminePropertyName()
         {
             if (PropertyInfo == null)
             {
@@ -108,9 +138,9 @@ namespace System.Text.Json.Serialization
 
                 NameAsString = name;
             }
-            else if (options.PropertyNamingPolicy != null)
+            else if (Options.PropertyNamingPolicy != null)
             {
-                string name = options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name);
+                string name = Options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name);
                 if (name == null)
                 {
                     ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this);
@@ -129,7 +159,7 @@ namespace System.Text.Json.Serialization
             Name = Encoding.UTF8.GetBytes(NameAsString);
 
             // Set the compare name.
-            if (options.PropertyNameCaseInsensitive)
+            if (Options.PropertyNameCaseInsensitive)
             {
                 NameUsedToCompareAsString = NameAsString.ToUpperInvariant();
                 NameUsedToCompare = Encoding.UTF8.GetBytes(NameUsedToCompareAsString);
@@ -173,12 +203,12 @@ namespace System.Text.Json.Serialization
 #endif
         }
 
-        private void DetermineSerializationCapabilities(JsonSerializerOptions options)
+        private void DetermineSerializationCapabilities()
         {
             if (ClassType != ClassType.Enumerable && ClassType != ClassType.Dictionary)
             {
                 // We serialize if there is a getter + not ignoring readonly properties.
-                ShouldSerialize = HasGetter && (HasSetter || !options.IgnoreReadOnlyProperties);
+                ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties);
 
                 // We deserialize if there is a setter. 
                 ShouldDeserialize = HasSetter;
@@ -233,13 +263,13 @@ namespace System.Text.Json.Serialization
                             RuntimePropertyType.GetGenericArguments().Length == 1)
                         {
                             EnumerableConverter = s_jsonImmutableConverter;
-                            ((DefaultImmutableConverter)EnumerableConverter).RegisterImmutableCollectionType(RuntimePropertyType, elementType, options);
+                            ((DefaultImmutableConverter)EnumerableConverter).RegisterImmutableCollectionType(RuntimePropertyType, elementType, Options);
                         }
                     }
                 }
                 else
                 {
-                    ShouldSerialize = HasGetter && !options.IgnoreReadOnlyProperties;
+                    ShouldSerialize = HasGetter && !Options.IgnoreReadOnlyProperties;
                 }
             }
         }
@@ -264,8 +294,9 @@ namespace System.Text.Json.Serialization
         public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo propertyInfo, JsonSerializerOptions options)
         {
             JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfoNotNullable<int, int, int>();
+            jsonPropertyInfo.Options = options;
             jsonPropertyInfo.PropertyInfo = propertyInfo;
-            jsonPropertyInfo.DeterminePropertyName(options);
+            jsonPropertyInfo.DeterminePropertyName();
 
             Debug.Assert(!jsonPropertyInfo.ShouldDeserialize);
             Debug.Assert(!jsonPropertyInfo.ShouldSerialize);
@@ -288,13 +319,13 @@ namespace System.Text.Json.Serialization
 
         public abstract Type GetDictionaryConcreteType();
 
-        public abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
-        public abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
+        public abstract void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
+        public abstract void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
         public abstract void SetValueAsObject(object obj, object value);
 
-        public abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
+        public abstract void Write(ref WriteStackFrame current, Utf8JsonWriter writer);
 
-        public virtual void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { }
-        public abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
+        public virtual void WriteDictionary(ref WriteStackFrame current, Utf8JsonWriter writer) { }
+        public abstract void WriteEnumerable(ref WriteStackFrame current, Utf8JsonWriter writer);
     }
 }
index 7a9aa06..22be588 100644 (file)
@@ -53,13 +53,13 @@ namespace System.Text.Json.Serialization
                 ValueConverter = DefaultConverters<TRuntimeProperty>.s_converter;
             }
 
-            GetPolicies(options);
+            GetPolicies();
         }
 
-        public override void GetPolicies(JsonSerializerOptions options)
+        public override void GetPolicies()
         {
             ValueConverter = DefaultConverters<TRuntimeProperty>.s_converter;
-            base.GetPolicies(options);
+            base.GetPolicies();
         }
 
         public override object GetValueAsObject(object obj)
index 680f9c3..6fdeb2d 100644 (file)
@@ -14,7 +14,7 @@ namespace System.Text.Json.Serialization
         JsonPropertyInfoCommon<TClass, TDeclaredProperty, TRuntimeProperty>
         where TRuntimeProperty : TDeclaredProperty
     {
-        public override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+        public override void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
         {
             Debug.Assert(ShouldDeserialize);
 
@@ -22,7 +22,7 @@ namespace System.Text.Json.Serialization
             {
                 // Forward the setter to the value-based JsonPropertyInfo.
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
-                propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
+                propertyInfo.ReadEnumerable(tokenType, ref state, ref reader);
             }
             else
             {
@@ -48,7 +48,7 @@ namespace System.Text.Json.Serialization
         }
 
         // If this method is changed, also change JsonPropertyInfoNullable.ReadEnumerable and JsonSerializer.ApplyObjectToEnumerable
-        public override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+        public override void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
         {
             Debug.Assert(ShouldDeserialize);
 
@@ -61,7 +61,7 @@ namespace System.Text.Json.Serialization
             JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader);
         }
 
-        public override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
+        public override void Write(ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             Debug.Assert(current.Enumerator == null);
             Debug.Assert(ShouldSerialize);
@@ -98,13 +98,13 @@ namespace System.Text.Json.Serialization
             }
         }
 
-        public override void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
+        public override void WriteDictionary(ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             Debug.Assert(ShouldSerialize);
-            JsonSerializer.WriteDictionary(ValueConverter, options, ref current, writer);
+            JsonSerializer.WriteDictionary(ValueConverter, Options, ref current, writer);
         }
 
-        public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
+        public override void WriteEnumerable(ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             Debug.Assert(ShouldSerialize);
 
index 9034f18..b52a828 100644 (file)
@@ -17,7 +17,7 @@ namespace System.Text.Json.Serialization
         // should this be cached somewhere else so that it's not populated per TClass as well as TProperty?
         private static readonly Type s_underlyingType = typeof(TProperty);
 
-        public override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+        public override void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
         {
             Debug.Assert(ElementClassInfo == null);
             Debug.Assert(ShouldDeserialize);
@@ -39,7 +39,7 @@ namespace System.Text.Json.Serialization
             ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.PropertyPath);
         }
 
-        public override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+        public override void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
         {
             Debug.Assert(ShouldDeserialize);
 
@@ -53,7 +53,7 @@ namespace System.Text.Json.Serialization
             JsonSerializer.ApplyValueToEnumerable(ref nullableValue, ref state, ref reader);
         }
 
-        public override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
+        public override void Write(ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             Debug.Assert(ShouldSerialize);
 
@@ -61,7 +61,7 @@ namespace System.Text.Json.Serialization
             {
                 // Forward the setter to the value-based JsonPropertyInfo.
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
-                propertyInfo.WriteEnumerable(options, ref current, writer);
+                propertyInfo.WriteEnumerable(ref current, writer);
             }
             else
             {
@@ -98,7 +98,7 @@ namespace System.Text.Json.Serialization
             }
         }
 
-        public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
+        public override void WriteEnumerable(ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             Debug.Assert(ShouldSerialize);
 
index 97540f5..27f939b 100644 (file)
@@ -50,7 +50,7 @@ namespace System.Text.Json.Serialization
             if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue) == null)
             {
                 // Create the dictionary.
-                JsonClassInfo dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType);
+                JsonClassInfo dictionaryClassInfo = jsonPropertyInfo.RuntimeClassInfo;
                 IDictionary value = (IDictionary)dictionaryClassInfo.CreateObject();
                 if (value != null)
                 {
index e7b0ff2..3c0bde8 100644 (file)
@@ -101,17 +101,15 @@ namespace System.Text.Json.Serialization
             IDictionary extensionData = (IDictionary)jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
             if (extensionData == null)
             {
-                Type type = jsonPropertyInfo.DeclaredPropertyType;
-
                 // Create the appropriate dictionary type. We already verified the types.
-                Debug.Assert(type.IsGenericType);
-                Debug.Assert(type.GetGenericArguments().Length == 2);
-                Debug.Assert(type.GetGenericArguments()[0].UnderlyingSystemType == typeof(string));
+                Debug.Assert(jsonPropertyInfo.DeclaredPropertyType.IsGenericType);
+                Debug.Assert(jsonPropertyInfo.DeclaredPropertyType.GetGenericArguments().Length == 2);
+                Debug.Assert(jsonPropertyInfo.DeclaredPropertyType.GetGenericArguments()[0].UnderlyingSystemType == typeof(string));
                 Debug.Assert(
-                    type.GetGenericArguments()[1].UnderlyingSystemType == typeof(object) ||
-                    type.GetGenericArguments()[1].UnderlyingSystemType == typeof(JsonElement));
+                    jsonPropertyInfo.DeclaredPropertyType.GetGenericArguments()[1].UnderlyingSystemType == typeof(object) ||
+                    jsonPropertyInfo.DeclaredPropertyType.GetGenericArguments()[1].UnderlyingSystemType == typeof(JsonElement));
 
-                extensionData = (IDictionary)options.GetOrAddClass(type).CreateObject();
+                extensionData = (IDictionary)jsonPropertyInfo.RuntimeClassInfo.CreateObject();
                 jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, extensionData);
             }
 
index a113129..82e01aa 100644 (file)
@@ -25,7 +25,7 @@ namespace System.Text.Json.Serialization
 
             bool lastCall = (!state.Current.IsProcessingEnumerableOrDictionary && state.Current.ReturnValue == null);
 
-            jsonPropertyInfo.Read(tokenType, options, ref state, ref reader);
+            jsonPropertyInfo.Read(tokenType, ref state, ref reader);
             return lastCall;
         }
     }
index b689a53..ccbccd5 100644 (file)
@@ -52,7 +52,7 @@ namespace System.Text.Json.Serialization
 
                     if (elementClassInfo.ClassType == ClassType.Value)
                     {
-                        elementClassInfo.GetPolicyProperty().WriteDictionary(options, ref state.Current, writer);
+                        elementClassInfo.GetPolicyProperty().WriteDictionary(ref state.Current, writer);
                     }
                     else if (state.Current.Enumerator.Current == null)
                     {
index 4be8b9f..07c1290 100644 (file)
@@ -48,7 +48,7 @@ namespace System.Text.Json.Serialization
 
                 if (elementClassInfo.ClassType == ClassType.Value)
                 {
-                    elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, writer);
+                    elementClassInfo.GetPolicyProperty().WriteEnumerable(ref state.Current, writer);
                 }
                 else if (state.Current.Enumerator.Current == null)
                 {
index f60932e..80275ee 100644 (file)
@@ -74,7 +74,7 @@ namespace System.Text.Json.Serialization
 
             if (jsonPropertyInfo.ClassType == ClassType.Value)
             {
-                jsonPropertyInfo.Write(options, ref state.Current, writer);
+                jsonPropertyInfo.Write(ref state.Current, writer);
                 state.Current.NextProperty();
                 return true;
             }
@@ -116,7 +116,7 @@ namespace System.Text.Json.Serialization
 
                 state.Current.NextProperty();
 
-                JsonClassInfo nextClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType);
+                JsonClassInfo nextClassInfo = jsonPropertyInfo.RuntimeClassInfo;
                 state.Push(nextClassInfo, currentValue);
 
                 // Set the PropertyInfo so we can obtain the property name in order to write it.
index ccbac22..c9a9e4a 100644 (file)
@@ -31,7 +31,7 @@ namespace System.Text.Json.Serialization
                         break;
                     case ClassType.Value:
                         Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value);
-                        current.JsonPropertyInfo.Write(options, ref current, writer);
+                        current.JsonPropertyInfo.Write(ref current, writer);
                         finishedSerializing = true;
                         break;
                     case ClassType.Object:
index 7f3e4d3..6170ba1 100644 (file)
@@ -136,7 +136,7 @@ namespace System.Text.Json.Serialization
             if (typeof(IList).IsAssignableFrom(propType))
             {
                 // If IList, add the members as we create them.
-                JsonClassInfo collectionClassInfo = options.GetOrAddClass(propType);
+                JsonClassInfo collectionClassInfo = state.Current.JsonPropertyInfo.RuntimeClassInfo;
                 IList collection = (IList)collectionClassInfo.CreateObject();
                 return collection;
             }
index 19f3856..e2f530e 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Collections.Generic;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -12,21 +13,69 @@ namespace System.Text.Json.Serialization.Tests
         public static void WriteCyclicFail()
         {
             TestClassWithCycle obj = new TestClassWithCycle();
-            obj.Initialize();
+            obj.Parent = obj;
 
-            // We don't allow cycles; we throw InvalidOperation instead of an unrecoverable StackOverflow.
+            // We don't allow graph cycles; we throw InvalidOperation instead of an unrecoverable StackOverflow.
             Assert.Throws<InvalidOperationException>(() => JsonSerializer.ToString(obj));
         }
 
         [Fact]
-        [ActiveIssue(37313)]
-        public static void WriteTestClassWithArrayOfElementsOfTheSameClassWithoutCyclesDoesNotFail()
+        public static void SimpleTypeCycle()
         {
             TestClassWithArrayOfElementsOfTheSameClass obj = new TestClassWithArrayOfElementsOfTheSameClass();
 
-            //It shouldn't throw when there is no real cycle reference, and just empty object is created
+            // A cycle in just Types (not data) is allowed.
             string json = JsonSerializer.ToString(obj);
-            Assert.Equal(@"{}", json);
+            Assert.Equal(@"{""Array"":null}", json);
+        }
+
+        [Fact]
+        public static void DeepTypeCycleWithRoundTrip()
+        {
+            TestClassWithCycle root = new TestClassWithCycle("root");
+            TestClassWithCycle parent = new TestClassWithCycle("parent");
+            root.Parent = parent;
+            root.Children.Add(new TestClassWithCycle("child1"));
+            root.Children.Add(new TestClassWithCycle("child2"));
+
+            // A cycle in just Types (not data) is allowed.
+            string json = JsonSerializer.ToString(root);
+
+            // Round-trip the JSON.
+            TestClassWithCycle rootCopy = JsonSerializer.Parse<TestClassWithCycle>(json);
+            Assert.Equal("root", rootCopy.Name);
+            Assert.Equal(2, rootCopy.Children.Count);
+
+            Assert.Equal("parent", rootCopy.Parent.Name);
+            Assert.Equal(0, rootCopy.Parent.Children.Count);
+            Assert.Null(rootCopy.Parent.Parent);
+
+            Assert.Equal("child1", rootCopy.Children[0].Name);
+            Assert.Equal(0, rootCopy.Children[0].Children.Count);
+            Assert.Null(rootCopy.Children[0].Parent);
+
+            Assert.Equal("child2", rootCopy.Children[1].Name);
+            Assert.Equal(0, rootCopy.Children[1].Children.Count);
+            Assert.Null(rootCopy.Children[1].Parent);
+        }
+
+        public class TestClassWithCycle
+        {
+            public TestClassWithCycle() { }
+
+            public TestClassWithCycle(string name)
+            {
+                Name = name;
+            }
+
+            public TestClassWithCycle Parent { get; set; }
+            public List<TestClassWithCycle> Children { get; set; } = new List<TestClassWithCycle>();
+            public string Name { get; set; }
+        }
+
+        public class TestClassWithArrayOfElementsOfTheSameClass
+        {
+            public TestClassWithArrayOfElementsOfTheSameClass[] Array { get; set; }
         }
     }
 }
index e645316..7f88ab2 100644 (file)
@@ -387,21 +387,6 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
-    public class TestClassWithCycle
-    {
-        public TestClassWithCycle Parent { get; set; }
-
-        public void Initialize()
-        {
-            Parent = this;
-        }
-    }
-
-    public class TestClassWithArrayOfElementsOfTheSameClass
-    {
-        public TestClassWithArrayOfElementsOfTheSameClass[] Array { get; set; }
-    }
-
     public class TestClassWithGenericList : ITestClass
     {
         public List<string> MyData { get; set; }