diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 6160df0ffb5f..07936ee7d9e9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -20,6 +20,11 @@ internal abstract class JsonPropertyInfo 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 @@ internal abstract class JsonPropertyInfo 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 @@ public virtual void Initialize( 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 @@ public virtual void Initialize( 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 @@ private void DeterminePropertyName(JsonSerializerOptions options) 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 @@ private void DeterminePropertyName(JsonSerializerOptions options) 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 @@ private void DeterminePropertyName(JsonSerializerOptions options) #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 @@ private void DetermineSerializationCapabilities(JsonSerializerOptions options) 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 @@ public void CopyRuntimeSettingsTo(JsonPropertyInfo other) 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 @@ public static TAttribute GetAttribute(PropertyInfo propertyInfo) whe 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/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 7a9aa064ca07..22be588e0fa0 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -53,13 +53,13 @@ public override void Initialize( 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/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index 680f9c388bc7..6fdeb2d5f28d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -14,7 +14,7 @@ internal sealed class JsonPropertyInfoNotNullable 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 @@ public override void Read(JsonTokenType tokenType, JsonSerializerOptions options { // 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 @@ public override void Read(JsonTokenType tokenType, JsonSerializerOptions options } // 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 @@ public override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptio 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 @@ public override void Write(JsonSerializerOptions options, ref WriteStackFrame cu } } - 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/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 9034f184b939..b52a828f6a3b 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -17,7 +17,7 @@ internal sealed class JsonPropertyInfoNullable // 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 @@ public override void Read(JsonTokenType tokenType, JsonSerializerOptions options 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 @@ public override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptio 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 @@ public override void Write(JsonSerializerOptions options, ref WriteStackFrame cu { // 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 @@ public override void Write(JsonSerializerOptions options, ref WriteStackFrame cu } } - 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index 97540f55d25b..27f939b5f6e9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -50,7 +50,7 @@ private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs index e7b0ff2916a7..3c0bde8e7d81 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs @@ -101,17 +101,15 @@ private static void ProcessMissingProperty( 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs index a11312908c05..82e01aa13ed8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs @@ -25,7 +25,7 @@ private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions o 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs index b689a53d3d39..ccbccd531924 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs @@ -52,7 +52,7 @@ private static bool HandleDictionary( 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs index 4be8b9fa3464..07c1290e8882 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs @@ -48,7 +48,7 @@ private static bool HandleEnumerable( 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index f60932eac96d..80275ee6fd67 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -74,7 +74,7 @@ private static bool HandleObject( 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 @@ private static bool HandleObject( 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/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs index ccbac22d2dda..c9a9e4a762bb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs @@ -31,7 +31,7 @@ private static bool Write( 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/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index 7f3e4d3861d7..6170ba1d869f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -136,7 +136,7 @@ public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadSt 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/System.Text.Json/tests/Serialization/CyclicTests.cs b/src/System.Text.Json/tests/Serialization/CyclicTests.cs index 19f38562db3b..e2f530e26c95 100644 --- a/src/System.Text.Json/tests/Serialization/CyclicTests.cs +++ b/src/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 @@ public static class CyclicTests 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/System.Text.Json/tests/Serialization/TestClasses.cs b/src/System.Text.Json/tests/Serialization/TestClasses.cs index e64531659cd3..7f88ab2f26be 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.cs @@ -387,21 +387,6 @@ public void Verify() } } - 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; }