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;
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,
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; }
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)
{
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);
Name = Encoding.UTF8.GetBytes(NameAsString);
// Set the compare name.
- if (options.PropertyNameCaseInsensitive)
+ if (Options.PropertyNameCaseInsensitive)
{
NameUsedToCompareAsString = NameAsString.ToUpperInvariant();
NameUsedToCompare = Encoding.UTF8.GetBytes(NameUsedToCompareAsString);
#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;
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;
}
}
}
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);
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);
}
}
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)
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);
{
// 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
{
}
// 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);
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);
}
}
- 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);
// 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);
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);
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);
{
// Forward the setter to the value-based JsonPropertyInfo.
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
- propertyInfo.WriteEnumerable(options, ref current, writer);
+ propertyInfo.WriteEnumerable(ref current, writer);
}
else
{
}
}
- public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
+ public override void WriteEnumerable(ref WriteStackFrame current, Utf8JsonWriter writer)
{
Debug.Assert(ShouldSerialize);
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)
{
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);
}
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;
}
}
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)
{
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)
{
if (jsonPropertyInfo.ClassType == ClassType.Value)
{
- jsonPropertyInfo.Write(options, ref state.Current, writer);
+ jsonPropertyInfo.Write(ref state.Current, writer);
state.Current.NextProperty();
return true;
}
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.
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:
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;
}
// 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
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; }
}
}
}
}
}
- 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; }