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
Fix 3.0->3.1 regression in JSON serializing nested concurrent diction…
Browse files Browse the repository at this point in the history
…aries (#42772)

* Improve (de)serialization support for nested dictionaries

* Document test cases and add test for polymorphic element types

* Add note about test depth
  • Loading branch information
layomia authored and Anipik committed Jan 14, 2020
1 parent e021226 commit b855121
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public override Type GetDictionaryConcreteType()

public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
{
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TRuntimeProperty>> genericEnumerator)
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TDeclaredProperty>> genericEnumerator)
{
key = genericEnumerator.Current.Key;
value = genericEnumerator.Current.Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public override Type GetDictionaryConcreteType()

public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
{
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TRuntimeProperty>> genericEnumerator)
if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TDeclaredProperty>> genericEnumerator)
{
key = genericEnumerator.Current.Key;
value = genericEnumerator.Current.Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,26 @@ internal static void ApplyValueToEnumerable<TProperty>(
else if (state.Current.IsProcessingDictionary())
{
Debug.Assert(state.Current.ReturnValue != null);
IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);

object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);

string key = state.Current.KeyName;
Debug.Assert(!string.IsNullOrEmpty(key));
dictionary[key] = value;

if (currentDictionary is IDictionary<string, TProperty> genericDict)
{
Debug.Assert(!genericDict.IsReadOnly);
genericDict[key] = value;
}
else if (currentDictionary is IDictionary dict)
{
Debug.Assert(!dict.IsReadOnly);
dict[key] = value;
}
else
{
throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(currentDictionary.GetType(), parentType: null, memberInfo: null);
}
}
else if (state.Current.IsProcessingIDictionaryConstructible())
{
Expand Down
108 changes: 108 additions & 0 deletions src/System.Text.Json/tests/Serialization/DictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Reflection;
using System.Text.Encodings.Web;
using Xunit;
Expand Down Expand Up @@ -932,6 +934,112 @@ public static void DictionaryOfArrayOfDictionary()
Assert.Equal(JsonString, json);
}

private interface IClass { }

private class MyClass : IClass { }

private class MyNonGenericDictionary : Dictionary<string, int> { }

private class MyFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(IClass) || typeToConvert == typeof(MyClass);
}

public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return new MyStuffConverter();
}
}

private class MyStuffConverter : JsonConverter<IClass>
{
public override IClass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new MyClass();
}

public override void Write(Utf8JsonWriter writer, IClass value, JsonSerializerOptions options)
{
writer.WriteNumberValue(1);
}
}

// This method generates 316 unique test cases for nested dictionaries up to 4
// levels deep, along with matching JSON, encompassing the various planes of
// dictionaries that can be combined: generic, non-generic, BCL, user-derived,
// immutable, mutable, readonly, concurrent, specialized.
private static IEnumerable<(Type, string)> NestedDictionaryTypeData()
{
string testJson = @"{""Key"":1}";

List<Type> genericDictTypes = new List<Type>()
{
typeof(IDictionary<,>),
typeof(ConcurrentDictionary<,>),
typeof(GenericIDictionaryWrapper<,>),
};

List<Type> nonGenericDictTypes = new List<Type>()
{
typeof(Hashtable),
typeof(OrderedDictionary),
};

List<Type> baseDictionaryTypes = new List<Type>
{
typeof(MyNonGenericDictionary),
typeof(IReadOnlyDictionary<string, MyClass>),
typeof(ConcurrentDictionary<string, int>),
typeof(ImmutableDictionary<string, IClass>),
typeof(GenericIDictionaryWrapper<string, int?>),
};
baseDictionaryTypes.AddRange(nonGenericDictTypes);

// This method has exponential behavior which this depth value significantly impacts.
// Don't change this value without checking how many test cases are generated and
// how long the tests run for.
int maxTestDepth = 4;

HashSet<(Type, string)> tests = new HashSet<(Type, string)>();

for (int i = 0; i < maxTestDepth; i++)
{
List<Type> newBaseTypes = new List<Type>();

foreach (Type testType in baseDictionaryTypes)
{
tests.Add((testType, testJson));

foreach (Type genericType in genericDictTypes)
{
newBaseTypes.Add(genericType.MakeGenericType(typeof(string), testType));
}

newBaseTypes.AddRange(nonGenericDictTypes);
}

baseDictionaryTypes = newBaseTypes;
testJson = @"{""Key"":" + testJson + "}";
}

return tests;
}

[Fact]
public static void NestedDictionariesRoundtrip()
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new MyFactory());

foreach ((Type dictionaryType, string testJson) in NestedDictionaryTypeData())
{
object dict = JsonSerializer.Deserialize(testJson, dictionaryType, options);
Assert.Equal(testJson, JsonSerializer.Serialize(dict, options));
}
}

[Fact]
public static void DictionaryOfClasses()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,76 @@ IEnumerator IEnumerable.GetEnumerator()
}
}

public class GenericIDictionaryWrapper<TKey, TValue> : IDictionary<TKey, TValue>
{
private Dictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();

public TValue this[TKey key] { get => ((IDictionary<TKey, TValue>)_dict)[key]; set => ((IDictionary<TKey, TValue>)_dict)[key] = value; }

public ICollection<TKey> Keys => ((IDictionary<TKey, TValue>)_dict).Keys;

public ICollection<TValue> Values => ((IDictionary<TKey, TValue>)_dict).Values;

public int Count => ((IDictionary<TKey, TValue>)_dict).Count;

public bool IsReadOnly => ((IDictionary<TKey, TValue>)_dict).IsReadOnly;

public void Add(TKey key, TValue value)
{
((IDictionary<TKey, TValue>)_dict).Add(key, value);
}

public void Add(KeyValuePair<TKey, TValue> item)
{
((IDictionary<TKey, TValue>)_dict).Add(item);
}

public void Clear()
{
((IDictionary<TKey, TValue>)_dict).Clear();
}

public bool Contains(KeyValuePair<TKey, TValue> item)
{
return ((IDictionary<TKey, TValue>)_dict).Contains(item);
}

public bool ContainsKey(TKey key)
{
return ((IDictionary<TKey, TValue>)_dict).ContainsKey(key);
}

public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((IDictionary<TKey, TValue>)_dict).CopyTo(array, arrayIndex);
}

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ((IDictionary<TKey, TValue>)_dict).GetEnumerator();
}

public bool Remove(TKey key)
{
return ((IDictionary<TKey, TValue>)_dict).Remove(key);
}

public bool Remove(KeyValuePair<TKey, TValue> item)
{
return ((IDictionary<TKey, TValue>)_dict).Remove(item);
}

public bool TryGetValue(TKey key, out TValue value)
{
return ((IDictionary<TKey, TValue>)_dict).TryGetValue(key, out value);
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<TKey, TValue>)_dict).GetEnumerator();
}
}

public class StringToStringIReadOnlyDictionaryWrapper : IReadOnlyDictionary<string, string>
{
private Dictionary<string, string> _dictionary = new Dictionary<string, string>();
Expand Down

0 comments on commit b855121

Please sign in to comment.