From 9c5fd5cc3653a87b6dcfd356bea5652dcff948e1 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 21 May 2019 11:50:53 -0700 Subject: [PATCH] Avoid StackOverflowException when cyclic enumerable Type (dotnet/corefx#37818) Commit migrated from https://github.com/dotnet/corefx/commit/857d1d1a9e1cd3597e3b1488f119d3a52e8b921a --- .../Text/Json/Serialization/JsonPropertyInfo.cs | 85 +++++++++++++++------- .../Json/Serialization/JsonPropertyInfoCommon.cs | 6 +- .../Serialization/JsonPropertyInfoNotNullable.cs | 14 ++-- .../Json/Serialization/JsonPropertyInfoNullable.cs | 10 +-- .../JsonSerializer.Read.HandleDictionary.cs | 2 +- .../JsonSerializer.Read.HandlePropertyName.cs | 14 ++-- .../JsonSerializer.Read.HandleValue.cs | 2 +- .../JsonSerializer.Write.HandleDictionary.cs | 2 +- .../JsonSerializer.Write.HandleEnumerable.cs | 2 +- .../JsonSerializer.Write.HandleObject.cs | 4 +- .../Json/Serialization/JsonSerializer.Write.cs | 2 +- .../Text/Json/Serialization/ReadStackFrame.cs | 2 +- .../tests/Serialization/CyclicTests.cs | 61 ++++++++++++++-- .../tests/Serialization/TestClasses.cs | 15 ---- 14 files changed, 142 insertions(+), 79 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 6160df0..07936ee 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -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(); 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; + } + } + + /// + /// Return the JsonClassInfo for the element type, or null if the the property is not an enumerable or dictionary. + /// + 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(); + 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); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 7a9aa06..22be588 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -53,13 +53,13 @@ namespace System.Text.Json.Serialization ValueConverter = DefaultConverters.s_converter; } - GetPolicies(options); + GetPolicies(); } - public override void GetPolicies(JsonSerializerOptions options) + public override void GetPolicies() { ValueConverter = DefaultConverters.s_converter; - base.GetPolicies(options); + base.GetPolicies(); } public override object GetValueAsObject(object obj) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index 680f9c3..6fdeb2d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -14,7 +14,7 @@ namespace System.Text.Json.Serialization JsonPropertyInfoCommon 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); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 9034f18..b52a828 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -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); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index 97540f5..27f939b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -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) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs index e7b0ff2..3c0bde8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs @@ -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); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs index a113129..82e01aa 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs @@ -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; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs index b689a53..ccbccd5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs @@ -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) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs index 4be8b9f..07c1290 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs @@ -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) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index f60932e..80275ee 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -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. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs index ccbac22..c9a9e4a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs @@ -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: diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index 7f3e4d3..6170ba1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -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; } diff --git a/src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs index 19f3856..e2f530e 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs @@ -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(() => 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(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 Children { get; set; } = new List(); + public string Name { get; set; } + } + + public class TestClassWithArrayOfElementsOfTheSameClass + { + public TestClassWithArrayOfElementsOfTheSameClass[] Array { get; set; } } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs index e645316..7f88ab2 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs @@ -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 MyData { get; set; } -- 2.7.4