Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Support dictionaries that don't implement non-generic IDictionary (#4…
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter committed Oct 23, 2019
1 parent c33df88 commit f91669a
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ internal abstract class JsonPropertyInfo
private JsonClassInfo _runtimeClassInfo;
private JsonClassInfo _declaredTypeClassInfo;

private JsonPropertyInfo _dictionaryValuePropertyPolicy;

public bool CanBeNull { get; private set; }

public ClassType ClassType;
Expand Down Expand Up @@ -216,6 +218,39 @@ private void DetermineSerializationCapabilities()
}
}

/// <summary>
/// Return the JsonPropertyInfo for the TValue in IDictionary{string, TValue} when deserializing.
/// </summary>
/// <remarks>
/// This should not be called during warm-up (initial creation of JsonPropertyInfos) to avoid recursive behavior
/// which could result in a StackOverflowException.
/// </remarks>
public JsonPropertyInfo DictionaryValuePropertyPolicy
{
get
{
// Use the existing PolicyProperty if there is one.
if ((_dictionaryValuePropertyPolicy = ElementClassInfo.PolicyProperty) == null)
{
Debug.Assert(ClassType == ClassType.Dictionary || ClassType == ClassType.IDictionaryConstructible);

Type dictionaryValueType = ElementType;
Debug.Assert(ElementType != null);

_dictionaryValuePropertyPolicy = JsonClassInfo.CreateProperty(
declaredPropertyType: dictionaryValueType,
runtimePropertyType: dictionaryValueType,
implementedPropertyType: dictionaryValueType,
propertyInfo: null,
parentClassType: dictionaryValueType,
converter: null,
options: Options);
}

return _dictionaryValuePropertyPolicy;
}
}

/// <summary>
/// Return the JsonClassInfo for the element type, or null if the property is not an enumerable or dictionary.
/// </summary>
Expand Down Expand Up @@ -254,9 +289,39 @@ public static TAttribute GetAttribute<TAttribute>(PropertyInfo propertyInfo) whe
return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
}

public abstract Type GetConcreteType(Type type);

public abstract Type GetDictionaryConcreteType();

public abstract Type GetConcreteType(Type type);
public void GetDictionaryKeyAndValue(ref WriteStackFrame writeStackFrame, out string key, out object value)
{
Debug.Assert(ClassType == ClassType.Dictionary ||
ClassType == ClassType.IDictionaryConstructible);

if (writeStackFrame.CollectionEnumerator is IDictionaryEnumerator iDictionaryEnumerator)
{
if (iDictionaryEnumerator.Key is string keyAsString)
{
// Since IDictionaryEnumerator is not based on generics we can obtain the value directly.
key = keyAsString;
value = iDictionaryEnumerator.Value;
}
else
{
throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
writeStackFrame.JsonPropertyInfo.DeclaredPropertyType,
writeStackFrame.JsonPropertyInfo.ParentClassType,
writeStackFrame.JsonPropertyInfo.PropertyInfo);
}
}
else
{
// Forward to the generic dictionary.
DictionaryValuePropertyPolicy.GetDictionaryKeyAndValueFromGenericDictionary(ref writeStackFrame, out key, out value);
}
}

public abstract void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value);

public virtual void GetPolicies()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
using System.Collections.Generic;
using System.Diagnostics;

Expand Down Expand Up @@ -126,5 +127,21 @@ public override Type GetDictionaryConcreteType()
{
return typeof(Dictionary<string, TRuntimeProperty>);
}

public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
{
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TRuntimeProperty>> genericEnumerator)
{
key = genericEnumerator.Current.Key;
value = genericEnumerator.Current.Value;
}
else
{
throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
writeStackFrame.JsonPropertyInfo.DeclaredPropertyType,
writeStackFrame.JsonPropertyInfo.ParentClassType,
writeStackFrame.JsonPropertyInfo.PropertyInfo);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
using System.Collections.Generic;
using System.Diagnostics;

Expand Down Expand Up @@ -128,5 +129,21 @@ public override Type GetDictionaryConcreteType()
{
return typeof(Dictionary<string, TRuntimeProperty>);
}

public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
{
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TRuntimeProperty>> genericEnumerator)
{
key = genericEnumerator.Current.Key;
value = genericEnumerator.Current.Value;
}
else
{
throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
writeStackFrame.JsonPropertyInfo.DeclaredPropertyType,
writeStackFrame.JsonPropertyInfo.ParentClassType,
writeStackFrame.JsonPropertyInfo.PropertyInfo);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,21 @@ public override Type GetDictionaryConcreteType()
{
return typeof(Dictionary<string, TProperty?>);
}

public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
{
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TProperty?>> genericEnumerator)
{
key = genericEnumerator.Current.Key;
value = genericEnumerator.Current.Value;
}
else
{
throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
writeStackFrame.JsonPropertyInfo.DeclaredPropertyType,
writeStackFrame.JsonPropertyInfo.ParentClassType,
writeStackFrame.JsonPropertyInfo.PropertyInfo);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,11 @@ private static bool HandleDictionary(
return true;
}

if (enumerable is IDictionary dictionary)
{
state.Current.CollectionEnumerator = dictionary.GetEnumerator();
}
else
{
state.Current.CollectionEnumerator = enumerable.GetEnumerator();
}
// Let the dictionary return the default IEnumerator from its IEnumerable.GetEnumerator().
// For IDictionary-derived classes this is normally be IDictionaryEnumerator.
// For IDictionary<TKey, TVale>-derived classes this is normally IDictionaryEnumerator as well
// but may be IEnumerable<KeyValuePair<TKey, TValue>> if the dictionary only supports generics.
state.Current.CollectionEnumerator = enumerable.GetEnumerator();

if (state.Current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing)
{
Expand All @@ -58,28 +55,35 @@ private static bool HandleDictionary(

if (state.Current.CollectionEnumerator.MoveNext())
{
// A dictionary should not have a null KeyValuePair.
Debug.Assert(state.Current.CollectionEnumerator.Current != null);

bool obtainedValues = false;
string key = default;
object value = default;

// Check for polymorphism.
if (elementClassInfo.ClassType == ClassType.Unknown)
{
object currentValue = ((IDictionaryEnumerator)state.Current.CollectionEnumerator).Entry.Value;
GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
jsonPropertyInfo.GetDictionaryKeyAndValue(ref state.Current, out key, out value);
GetRuntimeClassInfo(value, ref elementClassInfo, options);
obtainedValues = true;
}

if (elementClassInfo.ClassType == ClassType.Value)
{
elementClassInfo.PolicyProperty.WriteDictionary(ref state, writer);
}
else if (state.Current.CollectionEnumerator.Current == null)
{
writer.WriteNull(jsonPropertyInfo.Name);
}
else
{
if (!obtainedValues)
{
jsonPropertyInfo.GetDictionaryKeyAndValue(ref state.Current, out key, out value);
}

// An object or another enumerator requires a new stack frame.
var enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
object value = enumerator.Value;
state.Push(elementClassInfo, value);
state.Current.KeyName = (string)enumerator.Key;
state.Current.KeyName = key;
}

return false;
Expand Down Expand Up @@ -127,15 +131,14 @@ internal static void WriteDictionary<TProperty>(
key = polymorphicEnumerator.Current.Key;
value = (TProperty)polymorphicEnumerator.Current.Value;
}
else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
else if (current.CollectionEnumerator is IDictionaryEnumerator iDictionaryEnumerator &&
iDictionaryEnumerator.Key is string keyAsString)
{
key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key;
value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
key = keyAsString;
value = (TProperty)iDictionaryEnumerator.Value;
}
else
{
// Todo: support non-generic Dictionary here (IDictionaryEnumerator)
// https://github.com/dotnet/corefx/issues/41034
throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
current.JsonPropertyInfo.DeclaredPropertyType,
current.JsonPropertyInfo.ParentClassType,
Expand Down
Loading

0 comments on commit f91669a

Please sign in to comment.