Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
deffe41
Add support for immutable dictionaries
layomia May 15, 2019
e36d301
Merge branch 'master' into dict
layomia May 16, 2019
3689997
Some clean up
layomia May 16, 2019
39216c8
Address review comments
layomia May 16, 2019
bf7942e
Address review comments
layomia May 17, 2019
48ca3be
Merge with master
layomia May 21, 2019
fcc160b
Merge with master
layomia May 22, 2019
114f82f
Remove commented out code
layomia May 22, 2019
11522a5
Move object property infos to JsonSerializerOptions
layomia May 23, 2019
25f675e
Merge with master
layomia May 25, 2019
4cf3cfa
Re-add immutable test
layomia May 25, 2019
54f8b81
Merge remote-tracking branch 'upstream/master' into dict
layomia May 28, 2019
c2994d1
Cache IsImmutableDict bool
layomia May 28, 2019
09fc112
Merge remote-tracking branch 'upstream/master' into dict
layomia May 29, 2019
557a2c6
Merge remote-tracking branch 'upstream/master' into dict
layomia May 29, 2019
a416371
Merge remote-tracking branch 'upstream/master' into dict
layomia May 29, 2019
a090ec0
More immutable support
layomia May 29, 2019
bf16dff
Merge remote-tracking branch 'upstream/master' into dict
layomia May 30, 2019
ae5f02d
Merge remote-tracking branch 'upstream/master' into dict
layomia May 30, 2019
89a2e48
Merge remote-tracking branch 'upstream/master' into dict
layomia May 30, 2019
cdcdbab
Merge remote-tracking branch 'upstream/master' into dict
layomia May 30, 2019
3eb0932
Merge remote-tracking branch 'upstream/master' into dict
layomia May 30, 2019
eccda71
Modify processing enumerable or dictionary check
layomia May 30, 2019
02d418a
Merge remote-tracking branch 'upstream/master' into dict
layomia May 30, 2019
97a5562
Correct handle end dictionary checks
layomia May 30, 2019
a6c9062
Re-add dict tests
layomia May 30, 2019
7adef5f
Merge with master
layomia May 31, 2019
6f3ad91
Merge remote-tracking branch 'upstream/master' into dict
layomia May 31, 2019
4ad4770
React to changes from master
layomia May 31, 2019
350a324
Merge remote-tracking branch 'upstream/master' into dict
layomia May 31, 2019
03906c6
Fix merge conflict
layomia May 31, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,59 @@
// 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;
using System.Reflection;
using System.Text.Json.Serialization.Converters;

namespace System.Text.Json.Serialization
{
internal abstract class ClassMaterializer
{
public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType);

public abstract object ImmutableCreateRange(Type constructingType, Type elementType);
public abstract object ImmutableCollectionCreateRange(Type constructingType, Type elementType);
public abstract object ImmutableDictionaryCreateRange(Type constructingType, Type elementType);

protected MethodInfo ImmutableCreateRangeMethod(Type constructingType, Type elementType)
protected MethodInfo ImmutableCollectionCreateRangeMethod(Type constructingType, Type elementType)
{
MethodInfo createRangeMethod = FindImmutableCreateRangeMethod(constructingType);

if (createRangeMethod == null)
{
return null;
}

return createRangeMethod.MakeGenericMethod(elementType);
}

protected MethodInfo ImmutableDictionaryCreateRangeMethod(Type constructingType, Type elementType)
{
MethodInfo createRangeMethod = FindImmutableCreateRangeMethod(constructingType);

if (createRangeMethod == null)
{
return null;
}

return createRangeMethod.MakeGenericMethod(typeof(string), elementType);
}

private MethodInfo FindImmutableCreateRangeMethod(Type constructingType)
{
MethodInfo[] constructingTypeMethods = constructingType.GetMethods();
MethodInfo createRange = null;

foreach (MethodInfo method in constructingTypeMethods)
{
if (method.Name == "CreateRange" && method.GetParameters().Length == 1)
{
createRange = method;
break;
return method;
}
}

Debug.Assert(createRange != null);

return createRange.MakeGenericMethod(elementType);
// This shouldn't happen because constructingType should be an immutable type with
// a CreateRange method. `null` being returned here will cause a JsonException to be
// thrown when the desired CreateRange delegate is about to be invoked.
Comment thread
layomia marked this conversation as resolved.
Debug.Fail("Could not create the appropriate CreateRange method.");
Comment thread
layomia marked this conversation as resolved.
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ internal enum ClassType
Value = 2, // Data type with single value
Enumerable = 3, // IEnumerable
Dictionary = 4, // IDictionary
ImmutableDictionary = 5, // Immutable Dictionary
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,11 @@ namespace System.Text.Json.Serialization.Converters
{
internal sealed class DefaultIEnumerableConstructibleConverter : JsonEnumerableConverter
{
public static ConcurrentDictionary<Type, JsonPropertyInfo> s_objectJsonProperties = new ConcurrentDictionary<Type, JsonPropertyInfo>();

public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
{
Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType;
JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;

JsonPropertyInfo propertyInfo;
if (elementClassInfo.ClassType == ClassType.Object)
{
Type objectType = elementClassInfo.Type;

if (s_objectJsonProperties.ContainsKey(objectType))
{
propertyInfo = s_objectJsonProperties[objectType];
}
else
{
propertyInfo = JsonClassInfo.CreateProperty(objectType, objectType, null, typeof(object), options);
s_objectJsonProperties[objectType] = propertyInfo;
}
}
else
{
propertyInfo = elementClassInfo.GetPolicyProperty();
}

JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
return propertyInfo.CreateIEnumerableConstructibleType(enumerableType, sourceList);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,19 @@ internal sealed class DefaultImmutableConverter : JsonEnumerableConverter
private const string ImmutableHashSetGenericTypeName = "System.Collections.Immutable.ImmutableHashSet`1";
private const string ImmutableSetGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableSet`1";

private const string ImmutableDictionaryTypeName = "System.Collections.Immutable.ImmutableDictionary";
private const string ImmutableDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableDictionary`2";
private const string ImmutableDictionaryGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableDictionary`2";

private const string ImmutableSortedDictionaryTypeName = "System.Collections.Immutable.ImmutableSortedDictionary";
private const string ImmutableSortedDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableSortedDictionary`2";

internal delegate object ImmutableCreateRangeDelegate<T>(IEnumerable<T> items);
internal delegate object ImmutableDictCreateRangeDelegate<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> items);

public static ConcurrentDictionary<string, object> CreateRangeDelegates = new ConcurrentDictionary<string, object>();
private static ConcurrentDictionary<string, object> s_createRangeDelegates = new ConcurrentDictionary<string, object>();

private string GetConstructingTypeName(string immutableCollectionTypeName)
private static string GetConstructingTypeName(string immutableCollectionTypeName)
{
switch (immutableCollectionTypeName)
{
Expand All @@ -47,22 +55,27 @@ private string GetConstructingTypeName(string immutableCollectionTypeName)
return ImmutableListTypeName;
case ImmutableStackGenericTypeName:
case ImmutableStackGenericInterfaceTypeName:
return ImmutableStackTypeName;
return ImmutableStackTypeName;
case ImmutableQueueGenericTypeName:
case ImmutableQueueGenericInterfaceTypeName:
return ImmutableQueueTypeName;
return ImmutableQueueTypeName;
case ImmutableSortedSetGenericTypeName:
return ImmutableSortedSetTypeName;
return ImmutableSortedSetTypeName;
case ImmutableHashSetGenericTypeName:
case ImmutableSetGenericInterfaceTypeName:
return ImmutableHashSetTypeName;
return ImmutableHashSetTypeName;
case ImmutableDictionaryGenericTypeName:
case ImmutableDictionaryGenericInterfaceTypeName:
return ImmutableDictionaryTypeName;
case ImmutableSortedDictionaryGenericTypeName:
return ImmutableSortedDictionaryTypeName;
default:
// TODO: Refactor exception throw following serialization exception changes.
// TODO: Refactor exception throw following serialization exception changes.
throw new NotSupportedException(SR.Format(SR.DeserializeTypeNotSupported, immutableCollectionTypeName));
Comment thread
layomia marked this conversation as resolved.
}
}

private string GetDelegateKey(
private static string GetDelegateKey(
Type immutableCollectionType,
Type elementType,
out Type underlyingType,
Expand All @@ -76,13 +89,58 @@ private string GetDelegateKey(
return $"{constructingTypeName}:{elementType.FullName}";
}

public void RegisterImmutableCollectionType(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
internal static bool TypeIsImmutableDictionary(Type type)
{
if (!type.IsGenericType)
{
return false;
}

switch (type.GetGenericTypeDefinition().FullName)
{
case ImmutableDictionaryGenericTypeName:
case ImmutableDictionaryGenericInterfaceTypeName:
case ImmutableSortedDictionaryGenericTypeName:
return true;
default:
return false;
}
}

internal static bool TryGetCreateRangeDelegate(string delegateKey, out object createRangeDelegate)
{
return s_createRangeDelegates.TryGetValue(delegateKey, out createRangeDelegate) && createRangeDelegate != null;
}

internal static void RegisterImmutableCollection(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
{
// Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);

// Exit if we have registered this immutable collection type.
if (s_createRangeDelegates.ContainsKey(delegateKey))
{
return;
}

// Get the constructing type.
Type constructingType = underlyingType.Assembly.GetType(constructingTypeName);

// Create a delegate which will point to the CreateRange method.
object createRangeDelegate;
createRangeDelegate = options.ClassMaterializerStrategy.ImmutableCollectionCreateRange(constructingType, elementType);

// Cache the delegate
s_createRangeDelegates.TryAdd(delegateKey, createRangeDelegate);
}

internal static void RegisterImmutableDictionary(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
{
// Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);

// Exit if we have registered this immutable collection type.
if (CreateRangeDelegates.ContainsKey(delegateKey))
if (s_createRangeDelegates.ContainsKey(delegateKey))
{
return;
}
Expand All @@ -91,11 +149,11 @@ public void RegisterImmutableCollectionType(Type immutableCollectionType, Type e
Type constructingType = underlyingType.Assembly.GetType(constructingTypeName);

// Create a delegate which will point to the CreateRange method.
object createRangeDelegate = options.ClassMaterializerStrategy.ImmutableCreateRange(constructingType, elementType);
Debug.Assert(createRangeDelegate != null);
object createRangeDelegate;
createRangeDelegate = options.ClassMaterializerStrategy.ImmutableDictionaryCreateRange(constructingType, elementType);

// Cache the delegate
CreateRangeDelegates.TryAdd(delegateKey, createRangeDelegate);
s_createRangeDelegates.TryAdd(delegateKey, createRangeDelegate);
}

public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
Expand All @@ -104,31 +162,24 @@ public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList
Type elementType = state.Current.GetElementType();

string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
Debug.Assert(CreateRangeDelegates.ContainsKey(delegateKey));
Debug.Assert(s_createRangeDelegates.ContainsKey(delegateKey));

JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
return propertyInfo.CreateImmutableCollectionFromList(immutableCollectionType, delegateKey, sourceList, state.JsonPath);
}

JsonPropertyInfo propertyInfo;
if (elementClassInfo.ClassType == ClassType.Object)
{
Type objectType = elementClassInfo.Type;

if (DefaultIEnumerableConstructibleConverter.s_objectJsonProperties.ContainsKey(objectType))
{
propertyInfo = DefaultIEnumerableConstructibleConverter.s_objectJsonProperties[objectType];
}
else
{
propertyInfo = JsonClassInfo.CreateProperty(objectType, objectType, null, typeof(object), options);
DefaultIEnumerableConstructibleConverter.s_objectJsonProperties[objectType] = propertyInfo;
}
}
else
{
propertyInfo = elementClassInfo.GetPolicyProperty();
}
internal IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
{
Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
Type elementType = state.Current.GetElementType();

return propertyInfo.CreateImmutableCollectionFromList(delegateKey, sourceList);
string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
Debug.Assert(s_createRangeDelegates.ContainsKey(delegateKey));

JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
return propertyInfo.CreateImmutableCollectionFromDictionary(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// 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.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization.Converters;

namespace System.Text.Json.Serialization
{
Expand All @@ -25,7 +25,7 @@ private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInf
{
JsonPropertyInfo jsonInfo = CreateProperty(propertyType, propertyType, propertyInfo, classType, options);

// Convert interfaces to concrete types.
// Convert non-immutable dictionary interfaces to concrete types.
if (propertyType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary)
{
// If a polymorphic case, we have to wait until run-time values are processed.
Expand Down Expand Up @@ -65,6 +65,7 @@ internal static JsonPropertyInfo CreateProperty(Type declaredPropertyType, Type
{
case ClassType.Enumerable:
case ClassType.Dictionary:
case ClassType.ImmutableDictionary:
case ClassType.Unknown:
collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,33 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options)

DetermineExtensionDataProperty();
break;

case ClassType.Enumerable:
case ClassType.Dictionary:
// Add a single property that maps to the class type so we can have policies applied.
JsonPropertyInfo policyProperty = AddPolicyProperty(type, options);
{
// Add a single property that maps to the class type so we can have policies applied.
JsonPropertyInfo policyProperty = AddPolicyProperty(type, options);

// Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary).
CreateObject = options.ClassMaterializerStrategy.CreateConstructor(policyProperty.RuntimePropertyType);
// Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary).
CreateObject = options.ClassMaterializerStrategy.CreateConstructor(policyProperty.RuntimePropertyType);

// Create a ClassInfo that maps to the element type which is used for (de)serialization and policies.
Type elementType = GetElementType(type, parentType : null, memberInfo: null);
ElementClassInfo = options.GetOrAddClass(elementType);
// Create a ClassInfo that maps to the element type which is used for (de)serialization and policies.
Type elementType = GetElementType(type, parentType: null, memberInfo: null);
ElementClassInfo = options.GetOrAddClass(elementType);
}
break;
case ClassType.ImmutableDictionary:
{
// Add a single property that maps to the class type so we can have policies applied.
AddPolicyProperty(type, options);

Type elementType = GetElementType(type, parentType: null, memberInfo: null);

CreateObject = options.ClassMaterializerStrategy.CreateConstructor(
typeof(Dictionary<,>).MakeGenericType(typeof(string), elementType));
Comment thread
layomia marked this conversation as resolved.

// Create a ClassInfo that maps to the element type which is used for (de)serialization and policies.
ElementClassInfo = options.GetOrAddClass(elementType);
}
break;
case ClassType.Value:
case ClassType.Unknown:
Expand Down Expand Up @@ -373,7 +388,7 @@ public static Type GetElementType(Type propertyType, Type parentType, MemberInfo
Type[] args = propertyType.GetGenericArguments();
ClassType classType = GetClassType(propertyType);

if (classType == ClassType.Dictionary &&
if ((classType == ClassType.Dictionary || classType == ClassType.ImmutableDictionary) &&
args.Length >= 2 && // It is >= 2 in case there is a IDictionary<TKey, TValue, TSomeExtension>.
args[0].UnderlyingSystemType == typeof(string))
{
Expand Down Expand Up @@ -403,9 +418,14 @@ internal static ClassType GetClassType(Type type)
return ClassType.Value;
}

if (DefaultImmutableConverter.TypeIsImmutableDictionary(type))
{
return ClassType.ImmutableDictionary;
}

if (typeof(IDictionary).IsAssignableFrom(type) ||
(type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>)
|| type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))))
(type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))))
{
return ClassType.Dictionary;
}
Expand Down
Loading