From ae8d862f5428d36ac21f37c19554b20ce660f4c8 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sat, 20 Apr 2019 21:21:55 -0500 Subject: [PATCH 1/3] Add Dictionary support to JsonSerializer This partially addresses https://github.com/dotnet/corefx/issues/36024. For the intial version, only `Dictionary` is supported. --- .../Text/Json/Serialization/ClassType.cs | 1 + .../Text/Json/Serialization/JsonClassInfo.cs | 20 ++++++- .../Json/Serialization/JsonPropertyInfo.cs | 1 + .../JsonPropertyInfoNotNullable.cs | 18 +++++- .../Serialization/JsonPropertyInfoNullable.cs | 20 ++++++- .../JsonSerializer.Read.HandleArray.cs | 25 ++++++++- .../JsonSerializer.Read.HandleNull.cs | 6 ++ .../JsonSerializer.Read.HandleObject.cs | 55 +++++++++++++++++++ .../JsonSerializer.Read.HandleValue.cs | 4 +- .../Json/Serialization/JsonSerializer.Read.cs | 24 ++++++-- .../Serialization/JsonSerializer.Write.cs | 1 + .../Text/Json/Serialization/ReadStackFrame.cs | 27 ++++++++- .../Text/Json/Serialization/WriteStack.cs | 2 +- .../tests/Serialization/Object.ReadTests.cs | 1 + .../TestClasses.SimpleTestClass.cs | 8 ++- .../TestClasses.SimpleTestClassWithObject.cs | 8 ++- 16 files changed, 206 insertions(+), 15 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs index 030546d4e942..f5fe6ce2c730 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs @@ -13,5 +13,6 @@ internal enum ClassType Object = 1, // POCO or rich data type Value = 2, // Data type with single value Enumerable = 3, // IEnumerable + Dictionary = 4, // IDictionary } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 3baecf8052bb..9fee186585ea 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -111,7 +111,7 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options) } } } - else if (ClassType == ClassType.Enumerable) + else if (ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary) { // Add a single property that maps to the class type so we can have policies applied. AddProperty(type, propertyInfo : null, type, options); @@ -311,9 +311,18 @@ public static Type GetElementType(Type propertyType) elementType = propertyType.GetElementType(); if (elementType == null) { + Type[] args = propertyType.GetGenericArguments(); + if (propertyType.IsGenericType) { - elementType = propertyType.GetGenericArguments()[0]; + if (typeof(IDictionary).IsAssignableFrom(propertyType)) + { + elementType = args[1]; + } + else + { + elementType = args[0]; + } } else { @@ -335,12 +344,17 @@ internal static ClassType GetClassType(Type type) type = Nullable.GetUnderlyingType(type); } - // A Type is considered a value if it implements IConvertible. + // A Type is considered a value if it implements IConvertible or is a DateTimeOffset. if (typeof(IConvertible).IsAssignableFrom(type) || type == typeof(DateTimeOffset)) { return ClassType.Value; } + if (typeof(IDictionary).IsAssignableFrom(type)) + { + return ClassType.Dictionary; + } + if (typeof(IEnumerable).IsAssignableFrom(type)) { return ClassType.Enumerable; 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 448f5eb220fb..35374aeb08d5 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 @@ -253,6 +253,7 @@ internal TAttribute GetAttribute() where TAttribute : Attribute internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); + internal abstract void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options); internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); 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 ce4a94fcda26..631f8c6958c7 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 @@ -31,7 +31,12 @@ internal JsonPropertyInfoNotNullable( internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) { - if (ElementClassInfo != null) + if (state.Current.IsDictionary()) + { + JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty(); + propertyInfo.ReadDictionaryValue(tokenType, options, ref state, ref reader); + } + else if (ElementClassInfo != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); @@ -75,6 +80,17 @@ internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOpt JsonSerializer.ApplyValueToEnumerable(ref value, options, ref state.Current); } + internal override void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) + { + if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value)) + { + ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); + return; + } + + JsonSerializer.ApplyValueToDictionary(ref value, options, ref state.Current); + } + internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) { Debug.Assert(state.Current.JsonPropertyInfo != null); 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 44cc4365b673..f45f7435b895 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 @@ -31,7 +31,12 @@ internal JsonPropertyInfoNullable( internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) { - if (ElementClassInfo != null) + if (state.Current.IsDictionary()) + { + JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty(); + propertyInfo.ReadDictionaryValue(tokenType, options, ref state, ref reader); + } + else if (ElementClassInfo != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); @@ -73,6 +78,19 @@ internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOpt JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state.Current); } + internal override void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) + { + if (ValueConverter == null || !ValueConverter.TryRead(typeof(TProperty), ref reader, out TProperty value)) + { + ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); + return; + } + + // Converting to TProperty? here lets us share a common ApplyValue() with ApplyNullValue(). + TProperty? nullableValue = new TProperty?(value); + JsonSerializer.ApplyValueToDictionary(ref nullableValue, options, ref state.Current); + } + internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) { TProperty? nullableValue = null; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index ebd9fef785fd..ba09adf3e206 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -16,7 +16,20 @@ private static void HandleStartArray( ref Utf8JsonReader reader, ref ReadStack state) { - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + JsonPropertyInfo jsonPropertyInfo; + + if (state.Current.IsDictionary()) + { + JsonClassInfo classInfo = state.Current.JsonClassInfo.ElementClassInfo; + JsonPropertyInfo propertyInfo = classInfo.GetPolicyProperty(); + + state.Push(); + state.Current.JsonClassInfo = classInfo; + state.Current.JsonPropertyInfo = propertyInfo; + state.Current.IsNestedEnumerableInDict = true; + } + + jsonPropertyInfo = state.Current.JsonPropertyInfo; bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize; if (skip || state.Current.Skip()) @@ -121,6 +134,16 @@ private static bool HandleEndArray( state.Pop(); } + if (state.Current.IsNestedEnumerableInDict) + { + state.Pop(); + + Debug.Assert(state.Current.IsDictionary()); + ApplyObjectToDictionary(value, options, ref state.Current); + + return false; + } + if (lastFrame) { if (state.Current.ReturnValue == null) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index 6f1d136fb1a5..4dde01bf658f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -30,6 +30,12 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J ThrowHelper.ThrowJsonReaderException_DeserializeCannotBeNull(reader, state); } + if (state.Current.IsDictionary()) + { + ApplyObjectToDictionary(null, options, ref state.Current); + return false; + } + if (state.Current.IsEnumerable()) { ApplyObjectToEnumerable(null, options, ref state.Current); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index c5dd88dd2b89..30ce6cd6d562 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; namespace System.Text.Json.Serialization { @@ -22,8 +24,30 @@ private static void HandleStartObject(JsonSerializerOptions options, ref ReadSta // An array of objects either on the current property or on a list Type objType = state.Current.GetElementType(); state.Push(); + state.Current.JsonClassInfo = options.GetOrAddClass(objType); } + else if (state.Current.IsDictionary()) + { + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize; + if (skip || state.Current.Skip()) + { + // The dictionary is not being applied to the object. + state.Push(); + state.Current.Drain = true; + return; + } + + Type elementType = state.Current.JsonClassInfo.ElementClassInfo.Type; + + state.Current.TempDictKeys = new List(); + state.Current.TempDictValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); + state.Current.ReturnValue = (IDictionary)Activator.CreateInstance(state.Current.JsonClassInfo.Type); + + return; + } else if (state.Current.JsonPropertyInfo != null) { // Nested object @@ -47,6 +71,23 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current); + if (state.Current.IsDictionary()) + { + ReadStackFrame frame = state.Current; + + int keyCount = ((IList)frame.TempDictKeys).Count; + + Debug.Assert(keyCount == (frame.TempDictValues).Count); + + IList keys = frame.TempDictKeys; + IList values = frame.TempDictValues; + + for (int i = 0; i < keyCount; i++) + { + ((IDictionary)state.Current.ReturnValue).Add(keys[i], values[i]); + } + } + object value = state.Current.ReturnValue; if (isLastFrame) @@ -60,5 +101,19 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack ApplyObjectToEnumerable(value, options, ref state.Current); return false; } + + internal static void ApplyValueToDictionary(ref TProperty value, JsonSerializerOptions options, ref ReadStackFrame frame) + { + Debug.Assert(frame.IsDictionary()); + ((List)frame.TempDictValues).Add(value); + Debug.Assert(((IList)frame.TempDictKeys).Count == ((IList)frame.TempDictValues).Count); + } + + internal static void ApplyObjectToDictionary(object value, JsonSerializerOptions options, ref ReadStackFrame frame) + { + Debug.Assert(frame.IsDictionary()); + (frame.TempDictValues).Add(value); + Debug.Assert(((IList)frame.TempDictKeys).Count == ((IList)frame.TempDictValues).Count); + } } } 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 263e4ae08f87..e9a9c79f66bc 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 @@ -19,7 +19,9 @@ private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions o jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); } - bool lastCall = (!state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable() && state.Current.ReturnValue == null); + bool notParsingEnumerable = !state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable(); + bool lastCall = (notParsingEnumerable && !state.Current.IsDictionary() && state.Current.ReturnValue == null); + jsonPropertyInfo.Read(tokenType, options, ref state, ref reader); return lastCall; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index e31204546eca..e77106c32ee5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Collections.Generic; using System.Diagnostics; namespace System.Text.Json.Serialization @@ -42,13 +43,28 @@ private static void ReadCore( Debug.Assert(state.Current.JsonClassInfo != default); ReadOnlySpan propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current); - if (state.Current.JsonPropertyInfo == null) + + if (state.Current.IsDictionary()) { - state.Current.JsonPropertyInfo = s_missingProperty; + string keyName = reader.GetString(); + + if (options.DictionaryKeyPolicy != null) + { + keyName = options.DictionaryKeyPolicy.ConvertName(keyName); + } + + state.Current.TempDictKeys.Add(keyName); } + else + { + state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current); + if (state.Current.JsonPropertyInfo == null) + { + state.Current.JsonPropertyInfo = s_missingProperty; + } - state.Current.PropertyIndex++; + state.Current.PropertyIndex++; + } } } else if (tokenType == JsonTokenType.StartObject) 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 1913e739a8af..7db945766a32 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 @@ -35,6 +35,7 @@ private static bool Write( finishedSerializing = true; break; case ClassType.Object: + case ClassType.Dictionary: finishedSerializing = WriteObject(options, writer, ref state); break; default: 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 ee8cce374983..0795c46b6c6f 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 @@ -14,6 +14,11 @@ internal struct ReadStackFrame internal object ReturnValue; internal JsonClassInfo JsonClassInfo; + // Support Dictionary + internal List TempDictKeys; + internal IList TempDictValues; + internal bool IsNestedEnumerableInDict; + // Current property values internal JsonPropertyInfo JsonPropertyInfo; internal bool PopStackOnEndArray; @@ -32,7 +37,7 @@ internal struct ReadStackFrame internal void Initialize(Type type, JsonSerializerOptions options) { JsonClassInfo = options.GetOrAddClass(type); - if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable) + if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary) { JsonPropertyInfo = JsonClassInfo.GetPolicyProperty(); } @@ -61,6 +66,11 @@ internal bool IsEnumerable() return JsonClassInfo.ClassType == ClassType.Enumerable; } + internal bool IsDictionary() + { + return JsonClassInfo.ClassType == ClassType.Dictionary; + } + internal bool Skip() { return Drain || ReferenceEquals(JsonPropertyInfo, JsonSerializer.s_missingProperty); @@ -76,6 +86,16 @@ internal bool IsPropertyEnumerable() return false; } + internal bool IsPropertyADictionary() + { + if (JsonPropertyInfo != null) + { + return JsonPropertyInfo.ClassType == ClassType.Dictionary; + } + + return false; + } + public Type GetElementType() { if (IsPropertyEnumerable()) @@ -88,6 +108,11 @@ public Type GetElementType() return JsonClassInfo.ElementClassInfo.Type; } + if (IsDictionary()) + { + return JsonClassInfo.ElementClassInfo.Type; + } + return JsonPropertyInfo.RuntimePropertyType; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 897923f41b38..4b614a94b418 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -51,7 +51,7 @@ public void Push(JsonClassInfo nextClassInfo, object nextValue) } else { - Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown); + Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Dictionary || nextClassInfo.ClassType == ClassType.Unknown); Current.PopStackOnEndObject = true; } } diff --git a/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs b/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs index ae498e5965be..e876116fd066 100644 --- a/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs +++ b/src/System.Text.Json/tests/Serialization/Object.ReadTests.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 diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs index ba35451c3f7f..4ff64b270cc4 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs @@ -52,6 +52,7 @@ public class SimpleTestClass : ITestClass public ICollection MyStringICollectionT { get; set; } public IReadOnlyCollection MyStringIReadOnlyCollectionT { get; set; } public IReadOnlyList MyStringIReadOnlyListT { get; set; } + public Dictionary MyStringToStringDict { get; set; } public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}"; public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}"; @@ -74,7 +75,8 @@ public class SimpleTestClass : ITestClass @"""MyDecimal"" : 3.3," + @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + - @"""MyEnum"" : 2"; // int by default + @"""MyEnum"" : 2," + // int by default + @"""MyStringToStringDict"" : {""key"" : ""value""}"; private const string s_partialJsonArrays = @"""MyInt16Array"" : [1]," + @@ -150,6 +152,8 @@ public void Initialize() MyStringICollectionT = new string[] { "Hello" }; MyStringIReadOnlyCollectionT = new string[] { "Hello" }; MyStringIReadOnlyListT = new string[] { "Hello" }; + + MyStringToStringDict = new Dictionary { { "key", "value" } }; } public void Verify() @@ -198,6 +202,8 @@ public void Verify() Assert.Equal("Hello", MyStringICollectionT.First()); Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First()); Assert.Equal("Hello", MyStringIReadOnlyListT[0]); + + Assert.Equal("value", MyStringToStringDict["key"]); } } } diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs index f7265932aaa0..ff57dc66d5f4 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs @@ -50,6 +50,7 @@ public class SimpleTestClassWithObject : ITestClass public object MyStringICollectionT { get; set; } public object MyStringIReadOnlyCollectionT { get; set; } public object MyStringIReadOnlyListT { get; set; } + public object MyStringToStringDict { get; set; } public static readonly string s_json = @"{" + @@ -92,7 +93,8 @@ public class SimpleTestClassWithObject : ITestClass @"""MyStringIListT"" : [""Hello""]," + @"""MyStringICollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + - @"""MyStringIReadOnlyListT"" : [""Hello""]" + + @"""MyStringIReadOnlyListT"" : [""Hello""]," + + @"""MyStringToStringDict"" : {""key"" : ""value""}" + @"}"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); @@ -141,6 +143,8 @@ public void Initialize() MyStringICollectionT = new string[] { "Hello" }; MyStringIReadOnlyCollectionT = new string[] { "Hello" }; MyStringIReadOnlyListT = new string[] { "Hello" }; + + MyStringToStringDict = new Dictionary { { "key", "value" } }; } public void Verify() @@ -187,6 +191,8 @@ public void Verify() Assert.Equal("Hello", ((ICollection)MyStringICollectionT).First()); Assert.Equal("Hello", ((IReadOnlyCollection)MyStringIReadOnlyCollectionT).First()); Assert.Equal("Hello", ((IReadOnlyList)MyStringIReadOnlyListT)[0]); + + Assert.Equal("value", ((Dictionary)MyStringToStringDict)["key"]); } } } From 971c22ca0f9ea4221b91bdc3cb9f19c16133ba2e Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sat, 20 Apr 2019 21:23:47 -0500 Subject: [PATCH 2/3] Refactor --- .../src/System.Text.Json.csproj | 1 + .../JsonClassInfo.AddProperty.cs | 14 +- .../Text/Json/Serialization/JsonClassInfo.cs | 10 +- .../Json/Serialization/JsonPropertyInfo.cs | 6 +- .../Serialization/JsonPropertyInfoCommon.cs | 17 +++ .../JsonPropertyInfoNotNullable.cs | 24 +-- .../Serialization/JsonPropertyInfoNullable.cs | 26 +--- .../JsonSerializer.Read.HandleArray.cs | 47 +++--- .../JsonSerializer.Read.HandleNull.cs | 10 +- .../JsonSerializer.Read.HandleObject.cs | 63 +------- .../JsonSerializer.Read.HandleValue.cs | 3 +- .../Json/Serialization/JsonSerializer.Read.cs | 4 +- .../JsonSerializer.Write.HandleDictionary.cs | 141 ++++++++++++++++++ .../JsonSerializer.Write.HandleObject.cs | 12 ++ .../Serialization/JsonSerializer.Write.cs | 4 +- .../Text/Json/Serialization/ReadStackFrame.cs | 10 +- .../Json/Serialization/WriteStackFrame.cs | 8 +- .../tests/Serialization/DictionaryTests.cs | 49 ++++++ .../tests/System.Text.Json.Tests.csproj | 1 + 19 files changed, 310 insertions(+), 140 deletions(-) create mode 100644 src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs create mode 100644 src/System.Text.Json/tests/Serialization/DictionaryTests.cs diff --git a/src/System.Text.Json/src/System.Text.Json.csproj b/src/System.Text.Json/src/System.Text.Json.csproj index 32b551ad6684..5070433915f0 100644 --- a/src/System.Text.Json/src/System.Text.Json.csproj +++ b/src/System.Text.Json/src/System.Text.Json.csproj @@ -17,6 +17,7 @@ + diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 4f65a1d5a78d..1e5998046863 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -13,6 +13,16 @@ private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInf { JsonPropertyInfo jsonInfo = CreateProperty(propertyType, propertyType, propertyInfo, classType, options); + // Convert interfaces to concrete types. + if (propertyType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary) + { + Type newPropertyType = jsonInfo.ElementClassInfo.GetPolicyProperty().GetConcreteType(propertyType); + if (propertyType != newPropertyType) + { + jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options); + } + } + if (propertyInfo != null) { _propertyRefs.Add(new PropertyRef(GetKey(jsonInfo.CompareName), jsonInfo)); @@ -30,7 +40,7 @@ internal JsonPropertyInfo CreateProperty(Type declaredPropertyType, Type runtime { Type collectionElementType = null; ClassType propertyClassType = GetClassType(runtimePropertyType); - if (propertyClassType == ClassType.Enumerable) + if (propertyClassType == ClassType.Enumerable || propertyClassType == ClassType.Dictionary) { collectionElementType = GetElementType(runtimePropertyType); // todo: if collectionElementType is object, create loosely-typed collection (JsonArray). @@ -45,8 +55,6 @@ internal JsonPropertyInfo CreateProperty(Type declaredPropertyType, Type runtime } else { - // For now we only support polymorphism with base type == typeof(object). - Debug.Assert(declaredPropertyType == runtimePropertyType || declaredPropertyType == typeof(object)); propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,>).MakeGenericType(parentClassType, declaredPropertyType, runtimePropertyType); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 9fee186585ea..52047ba54a2f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -114,10 +114,14 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options) else if (ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary) { // Add a single property that maps to the class type so we can have policies applied. - AddProperty(type, propertyInfo : null, type, options); + JsonPropertyInfo jsonPropertyInfo = AddProperty(type, propertyInfo: null, 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(jsonPropertyInfo.RuntimePropertyType); // Create a ClassInfo that maps to the element type which is used for (de)serialization and policies. Type elementType = GetElementType(type); + ElementClassInfo = options.GetOrAddClass(elementType); } else if (ClassType == ClassType.Value) @@ -350,7 +354,9 @@ internal static ClassType GetClassType(Type type) return ClassType.Value; } - if (typeof(IDictionary).IsAssignableFrom(type)) + if (typeof(IDictionary).IsAssignableFrom(type) || + (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) + || type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)))) { return ClassType.Dictionary; } 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 35374aeb08d5..f40258686284 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 @@ -62,7 +62,7 @@ internal JsonPropertyInfo( ClassType = JsonClassInfo.GetClassType(runtimePropertyType); if (elementType != null) { - Debug.Assert(ClassType == ClassType.Enumerable); + Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary); ElementClassInfo = options.GetOrAddClass(elementType); } @@ -251,13 +251,15 @@ internal TAttribute GetAttribute() where TAttribute : Attribute internal abstract IList CreateConverterList(); + internal abstract Type GetConcreteType(Type interfaceType); + internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); - internal abstract void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options); internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); + internal abstract void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); internal abstract void WriteEnumerable(JsonSerializerOptions options, 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 b7365a8f7aea..f99150d10145 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,6 +53,11 @@ internal JsonPropertyInfoCommon( _isPropertyPolicy = true; HasGetter = true; HasSetter = true; + + if (ClassType == ClassType.Dictionary) + { + ValueConverter = DefaultConverters.s_converter; + } } GetPolicies(options); @@ -90,5 +95,17 @@ internal override IList CreateConverterList() { return new List(); } + + // Map interfaces to a well-known implementation. + internal override Type GetConcreteType(Type interfaceType) + { + if (interfaceType.IsAssignableFrom(typeof(IDictionary)) || + interfaceType.IsAssignableFrom(typeof(IReadOnlyDictionary))) + { + return typeof(Dictionary); + } + + return interfaceType; + } } } 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 631f8c6958c7..6dfea95e9009 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 @@ -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; using System.Reflection; @@ -31,12 +32,7 @@ internal JsonPropertyInfoNotNullable( internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) { - if (state.Current.IsDictionary()) - { - JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty(); - propertyInfo.ReadDictionaryValue(tokenType, options, ref state, ref reader); - } - else if (ElementClassInfo != null) + if (ElementClassInfo != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); @@ -80,17 +76,6 @@ internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOpt JsonSerializer.ApplyValueToEnumerable(ref value, options, ref state.Current); } - internal override void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) - { - if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value)) - { - ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); - return; - } - - JsonSerializer.ApplyValueToDictionary(ref value, options, ref state.Current); - } - internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) { Debug.Assert(state.Current.JsonPropertyInfo != null); @@ -143,6 +128,11 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame } } + internal override void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) + { + JsonSerializer.WriteDictionary(ValueConverter, options, ref current, writer); + } + internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (ValueConverter != null) 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 f45f7435b895..a3a31d09c6da 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 @@ -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.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; @@ -31,12 +32,7 @@ internal JsonPropertyInfoNullable( internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) { - if (state.Current.IsDictionary()) - { - JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty(); - propertyInfo.ReadDictionaryValue(tokenType, options, ref state, ref reader); - } - else if (ElementClassInfo != null) + if (ElementClassInfo != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); @@ -78,19 +74,6 @@ internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOpt JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state.Current); } - internal override void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) - { - if (ValueConverter == null || !ValueConverter.TryRead(typeof(TProperty), ref reader, out TProperty value)) - { - ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); - return; - } - - // Converting to TProperty? here lets us share a common ApplyValue() with ApplyNullValue(). - TProperty? nullableValue = new TProperty?(value); - JsonSerializer.ApplyValueToDictionary(ref nullableValue, options, ref state.Current); - } - internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) { TProperty? nullableValue = null; @@ -143,6 +126,11 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame } } + internal override void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) + { + JsonSerializer.WriteDictionary(ValueConverter, options, ref current, writer); + } + internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (ValueConverter != null) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index ba09adf3e206..f9d2ffdc2739 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -18,17 +18,6 @@ private static void HandleStartArray( { JsonPropertyInfo jsonPropertyInfo; - if (state.Current.IsDictionary()) - { - JsonClassInfo classInfo = state.Current.JsonClassInfo.ElementClassInfo; - JsonPropertyInfo propertyInfo = classInfo.GetPolicyProperty(); - - state.Push(); - state.Current.JsonClassInfo = classInfo; - state.Current.JsonPropertyInfo = propertyInfo; - state.Current.IsNestedEnumerableInDict = true; - } - jsonPropertyInfo = state.Current.JsonPropertyInfo; bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize; @@ -134,16 +123,6 @@ private static bool HandleEndArray( state.Pop(); } - if (state.Current.IsNestedEnumerableInDict) - { - state.Pop(); - - Debug.Assert(state.Current.IsDictionary()); - ApplyObjectToDictionary(value, options, ref state.Current); - - return false; - } - if (lastFrame) { if (state.Current.ReturnValue == null) @@ -198,6 +177,16 @@ internal static void ApplyObjectToEnumerable(object value, JsonSerializerOptions ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value); } } + else if (frame.IsDictionary()) + { + ((IDictionary)frame.ReturnValue).Add(frame.KeyName, value); + } + else if (frame.IsPropertyADictionary()) + { + Debug.Assert(frame.JsonPropertyInfo != null); + Debug.Assert(frame.ReturnValue != null); + ((IDictionary)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(frame.KeyName, value); + } else { Debug.Assert(frame.JsonPropertyInfo != null); @@ -206,7 +195,10 @@ internal static void ApplyObjectToEnumerable(object value, JsonSerializerOptions } // If this method is changed, also change ApplyObjectToEnumerable. - internal static void ApplyValueToEnumerable(ref TProperty value, JsonSerializerOptions options, ref ReadStackFrame frame) + internal static void ApplyValueToEnumerable( + ref TProperty value, + JsonSerializerOptions options, + ref ReadStackFrame frame) { if (frame.IsEnumerable()) { @@ -232,6 +224,17 @@ internal static void ApplyValueToEnumerable(ref TProperty value, Json ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value); } } + else if (frame.IsDictionary()) + { + // todo: use TryAdd and throw JsonReaderException + ((IDictionary)frame.ReturnValue).Add(frame.KeyName, value); + } + else if (frame.IsPropertyADictionary()) + { + Debug.Assert(frame.JsonPropertyInfo != null); + Debug.Assert(frame.ReturnValue != null); + ((IDictionary)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(frame.KeyName, value); + } else { Debug.Assert(frame.JsonPropertyInfo != null); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index 4dde01bf658f..c4ea0046def1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -30,19 +30,13 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J ThrowHelper.ThrowJsonReaderException_DeserializeCannotBeNull(reader, state); } - if (state.Current.IsDictionary()) - { - ApplyObjectToDictionary(null, options, ref state.Current); - return false; - } - - if (state.Current.IsEnumerable()) + if (state.Current.IsEnumerable() || state.Current.IsDictionary()) { ApplyObjectToEnumerable(null, options, ref state.Current); return false; } - if (state.Current.IsPropertyEnumerable()) + if (state.Current.IsPropertyEnumerable() || state.Current.IsPropertyADictionary()) { state.Current.JsonPropertyInfo.ApplyNullValue(options, ref state); return false; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index 30ce6cd6d562..1289b49deec7 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -2,10 +2,6 @@ // 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; - namespace System.Text.Json.Serialization { public static partial class JsonSerializer @@ -19,35 +15,17 @@ private static void HandleStartObject(JsonSerializerOptions options, ref ReadSta return; } - if (state.Current.IsEnumerable() || state.Current.IsPropertyEnumerable()) + if (state.Current.IsDictionary()) + { + // Fall through and treat as a return value. + } + else if (state.Current.IsEnumerable() || state.Current.IsPropertyEnumerable() || state.Current.IsPropertyADictionary()) { // An array of objects either on the current property or on a list Type objType = state.Current.GetElementType(); state.Push(); - state.Current.JsonClassInfo = options.GetOrAddClass(objType); } - else if (state.Current.IsDictionary()) - { - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; - - bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize; - if (skip || state.Current.Skip()) - { - // The dictionary is not being applied to the object. - state.Push(); - state.Current.Drain = true; - return; - } - - Type elementType = state.Current.JsonClassInfo.ElementClassInfo.Type; - - state.Current.TempDictKeys = new List(); - state.Current.TempDictValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); - state.Current.ReturnValue = (IDictionary)Activator.CreateInstance(state.Current.JsonClassInfo.Type); - - return; - } else if (state.Current.JsonPropertyInfo != null) { // Nested object @@ -71,23 +49,6 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current); - if (state.Current.IsDictionary()) - { - ReadStackFrame frame = state.Current; - - int keyCount = ((IList)frame.TempDictKeys).Count; - - Debug.Assert(keyCount == (frame.TempDictValues).Count); - - IList keys = frame.TempDictKeys; - IList values = frame.TempDictValues; - - for (int i = 0; i < keyCount; i++) - { - ((IDictionary)state.Current.ReturnValue).Add(keys[i], values[i]); - } - } - object value = state.Current.ReturnValue; if (isLastFrame) @@ -101,19 +62,5 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack ApplyObjectToEnumerable(value, options, ref state.Current); return false; } - - internal static void ApplyValueToDictionary(ref TProperty value, JsonSerializerOptions options, ref ReadStackFrame frame) - { - Debug.Assert(frame.IsDictionary()); - ((List)frame.TempDictValues).Add(value); - Debug.Assert(((IList)frame.TempDictKeys).Count == ((IList)frame.TempDictValues).Count); - } - - internal static void ApplyObjectToDictionary(object value, JsonSerializerOptions options, ref ReadStackFrame frame) - { - Debug.Assert(frame.IsDictionary()); - (frame.TempDictValues).Add(value); - Debug.Assert(((IList)frame.TempDictKeys).Count == ((IList)frame.TempDictValues).Count); - } } } 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 e9a9c79f66bc..51b51aa447e5 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 @@ -19,8 +19,7 @@ private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions o jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); } - bool notParsingEnumerable = !state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable(); - bool lastCall = (notParsingEnumerable && !state.Current.IsDictionary() && state.Current.ReturnValue == null); + bool lastCall = (!state.Current.IsProcessingEnumerableOrDictionary() && state.Current.ReturnValue == null); jsonPropertyInfo.Read(tokenType, options, ref state, ref reader); return lastCall; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index e77106c32ee5..3635039a1856 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -47,13 +47,13 @@ private static void ReadCore( if (state.Current.IsDictionary()) { string keyName = reader.GetString(); - if (options.DictionaryKeyPolicy != null) { keyName = options.DictionaryKeyPolicy.ConvertName(keyName); } - state.Current.TempDictKeys.Add(keyName); + state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty(); + state.Current.KeyName = keyName; } else { 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 new file mode 100644 index 000000000000..d826a6851720 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Json.Serialization.Policies; + +namespace System.Text.Json.Serialization +{ + public static partial class JsonSerializer + { + private static bool HandleDictionary( + JsonClassInfo elementClassInfo, + JsonSerializerOptions options, + Utf8JsonWriter writer, + ref WriteStack state) + { + Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Dictionary); + + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + if (!jsonPropertyInfo.ShouldSerialize) + { + // Ignore writing this property. + return true; + } + + if (state.Current.Enumerator == null) + { + IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + + if (enumerable == null) + { + // Write a null object or enumerable. + writer.WriteNull(jsonPropertyInfo.Name); + return true; + } + + state.Current.Enumerator = enumerable.GetEnumerator(); + + if (jsonPropertyInfo.Name == null) + { + writer.WriteStartObject(); + } + else + { + writer.WriteStartObject(jsonPropertyInfo.Name); + } + } + + if (state.Current.Enumerator.MoveNext()) + { + // Check for polymorphism. + if (elementClassInfo.ClassType == ClassType.Unknown) + { + //todo:test + object currentValue = ((IDictionaryEnumerator)(state.Current.Enumerator)).Entry; + GetRuntimeClassInfo(currentValue, ref elementClassInfo, options); + } + + if (elementClassInfo.ClassType == ClassType.Value) + { + elementClassInfo.GetPolicyProperty().WriteDictionary(options, ref state.Current, writer); + } + else if (state.Current.Enumerator.Current == null) + { + writer.WriteNull(jsonPropertyInfo.Name); + } + else + { + // An object or another enumerator requires a new stack frame. + object nextValue = state.Current.Enumerator.Current; + state.Push(elementClassInfo, nextValue); + } + + return false; + } + + // We are done enumerating. + writer.WriteEndObject(); + + state.Current.EndDictionary(); + + return true; + } + + internal static void WriteDictionary( + JsonValueConverter converter, + JsonSerializerOptions options, + ref WriteStackFrame current, + Utf8JsonWriter writer) + { + if (converter == null) + { + return; + } + + Debug.Assert(current.Enumerator != null); + + string key; + TProperty value; + if (current.Enumerator is IEnumerator> enumerator) + { + // Avoid boxing for strongly-typed enumerators such as returned from IDictionary + value = enumerator.Current.Value; + key = enumerator.Current.Key; + } + else + { + // Todo: support non-generic Dictionary here (IDictionaryEnumerator) + throw new NotSupportedException(); + } + + if (value == null) + { + writer.WriteNull(key); + } + else + { + byte[] pooledKey = null; + byte[] utf8Key = Encoding.UTF8.GetBytes(key); + int length = JsonWriterHelper.GetMaxEscapedLength(utf8Key.Length, 0); + + Span escapedKey = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (pooledKey = ArrayPool.Shared.Rent(length)); + + JsonWriterHelper.EscapeString(utf8Key, escapedKey, 0, out int written); + + converter.Write(escapedKey.Slice(0, written), value, writer); + + if (pooledKey != null) + { + ArrayPool.Shared.Return(pooledKey); + } + } + } + } +} 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 17c5c1c21dc8..8cddfd78f5af 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 @@ -93,6 +93,18 @@ private static bool HandleObject( return endOfEnumerable; } + // A property that returns a dictionary keeps the same stack frame. + if (jsonPropertyInfo.ClassType == ClassType.Dictionary) + { + bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); + if (endOfEnumerable) + { + state.Current.NextProperty(); + } + + return endOfEnumerable; + } + // A property that returns an object. if (!obtainedValue) { 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 7db945766a32..ccbac22d2dda 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 @@ -35,9 +35,11 @@ private static bool Write( finishedSerializing = true; break; case ClassType.Object: - case ClassType.Dictionary: finishedSerializing = WriteObject(options, writer, ref state); break; + case ClassType.Dictionary: + finishedSerializing = HandleDictionary(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); + break; default: Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown); 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 0795c46b6c6f..946815b949ec 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 @@ -15,9 +15,7 @@ internal struct ReadStackFrame internal JsonClassInfo JsonClassInfo; // Support Dictionary - internal List TempDictKeys; - internal IList TempDictValues; - internal bool IsNestedEnumerableInDict; + internal string KeyName; // Current property values internal JsonPropertyInfo JsonPropertyInfo; @@ -59,6 +57,12 @@ internal void ResetProperty() PopStackOnEndArray = false; EnumerableCreated = false; TempEnumerableValues = null; + KeyName = null; + } + + internal bool IsProcessingEnumerableOrDictionary() + { + return IsEnumerable() ||IsPropertyEnumerable() || IsDictionary() || IsPropertyADictionary(); } internal bool IsEnumerable() diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs index a9fc78e834d3..72e31672efbd 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs @@ -29,7 +29,7 @@ internal struct WriteStackFrame internal void Initialize(Type type, JsonSerializerOptions options) { JsonClassInfo = options.GetOrAddClass(type); - if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable) + if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary) { JsonPropertyInfo = JsonClassInfo.GetPolicyProperty(); } @@ -51,6 +51,12 @@ internal void EndObject() EndProperty(); } + internal void EndDictionary() + { + Enumerator = null; + EndProperty(); + } + internal void EndArray() { Enumerator = null; diff --git a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs new file mode 100644 index 000000000000..f09c74ce182c --- /dev/null +++ b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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 partial class DictionaryTests + { + [Fact] + public static void DirectReturn() + { + { + Dictionary obj = JsonSerializer.Parse>(@"{""Hello"":""World"", ""Hello2"":""World2""}"); + Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); + + string json = JsonSerializer.ToString(obj); + + // Round-trip the json + obj = JsonSerializer.Parse>(json); + Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); + } + + { + IDictionary obj = JsonSerializer.Parse>(@"{""Hello"":""World""}"); + Assert.Equal("World", obj["Hello"]); + + string json = JsonSerializer.ToString(obj); + + obj = JsonSerializer.Parse>(json); + Assert.Equal("World", obj["Hello"]); + } + + { + IReadOnlyDictionary obj = JsonSerializer.Parse>(@"{""Hello"":""World""}"); + Assert.Equal("World", obj["Hello"]); + + string json = JsonSerializer.ToString(obj); + + obj = JsonSerializer.Parse>(json); + Assert.Equal("World", obj["Hello"]); + } + } + } +} diff --git a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj index 55b32d14aef5..fee73de090ec 100644 --- a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -26,6 +26,7 @@ + From 3d732db5b363d8dea0f9354e83de3bc74d5b273a Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Sun, 21 Apr 2019 08:22:40 -0400 Subject: [PATCH 3/3] Add more tests --- .../Text/Json/Serialization/JsonClassInfo.cs | 2 +- .../JsonSerializer.Read.HandleArray.cs | 2 +- .../tests/Serialization/DictionaryTests.cs | 10 ++ .../tests/Serialization/Object.ReadTests.cs | 7 + .../TestClasses.SimpleTestClass.cs | 10 +- .../TestClasses.SimpleTestClassWithObject.cs | 10 +- .../tests/Serialization/TestClasses.cs | 167 ++++++++++++++++++ 7 files changed, 204 insertions(+), 4 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 52047ba54a2f..c97b0f6d2602 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -319,7 +319,7 @@ public static Type GetElementType(Type propertyType) if (propertyType.IsGenericType) { - if (typeof(IDictionary).IsAssignableFrom(propertyType)) + if (GetClassType(propertyType) == ClassType.Dictionary) { elementType = args[1]; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index f9d2ffdc2739..ab2efb34d9b7 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -185,7 +185,7 @@ internal static void ApplyObjectToEnumerable(object value, JsonSerializerOptions { Debug.Assert(frame.JsonPropertyInfo != null); Debug.Assert(frame.ReturnValue != null); - ((IDictionary)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(frame.KeyName, value); + frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value, options); } else { diff --git a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs index f09c74ce182c..0d984b96cc5a 100644 --- a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs +++ b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs @@ -18,6 +18,7 @@ public static void DirectReturn() Assert.Equal("World2", obj["Hello2"]); string json = JsonSerializer.ToString(obj); + Assert.Equal(@"{""Hello"":""World"",""Hello2"":""World2""}", json); // Round-trip the json obj = JsonSerializer.Parse>(json); @@ -30,6 +31,7 @@ public static void DirectReturn() Assert.Equal("World", obj["Hello"]); string json = JsonSerializer.ToString(obj); + Assert.Equal(@"{""Hello"":""World""}", json); obj = JsonSerializer.Parse>(json); Assert.Equal("World", obj["Hello"]); @@ -40,10 +42,18 @@ public static void DirectReturn() Assert.Equal("World", obj["Hello"]); string json = JsonSerializer.ToString(obj); + Assert.Equal(@"{""Hello"":""World""}", json); obj = JsonSerializer.Parse>(json); Assert.Equal("World", obj["Hello"]); } } + + [Fact] + public static void ThrowsOnDuplicateKeys() + { + // todo: this should throw a JsonReaderException + Assert.Throws(() => JsonSerializer.Parse>(@"{""Hello"":""World"", ""Hello"":""World""}")); + } } } diff --git a/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs b/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs index e876116fd066..4b41c4b3d2fd 100644 --- a/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs +++ b/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs @@ -49,5 +49,12 @@ public static void ParseUntyped() Assert.Throws(() => JsonSerializer.Parse("[]")); Assert.Throws(() => JsonSerializer.Parse("[]")); } + + [Fact] + public static void ReadClassWithStringToPrimitiveDictionary() + { + TestClassWithStringToPrimitiveDictionary obj = JsonSerializer.Parse(TestClassWithStringToPrimitiveDictionary.s_data); + obj.Verify(); + } } } diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs index 4ff64b270cc4..d5ed0cb6bfd7 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs @@ -53,6 +53,8 @@ public class SimpleTestClass : ITestClass public IReadOnlyCollection MyStringIReadOnlyCollectionT { get; set; } public IReadOnlyList MyStringIReadOnlyListT { get; set; } public Dictionary MyStringToStringDict { get; set; } + public IDictionary MyStringToStringIDict { get; set; } + public IReadOnlyDictionary MyStringToStringIReadOnlyDict { get; set; } public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}"; public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}"; @@ -76,7 +78,9 @@ public class SimpleTestClass : ITestClass @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + @"""MyEnum"" : 2," + // int by default - @"""MyStringToStringDict"" : {""key"" : ""value""}"; + @"""MyStringToStringDict"" : {""key"" : ""value""}," + + @"""MyStringToStringIDict"" : {""key"" : ""value""}," + + @"""MyStringToStringIReadOnlyDict"" : {""key"" : ""value""}"; private const string s_partialJsonArrays = @"""MyInt16Array"" : [1]," + @@ -154,6 +158,8 @@ public void Initialize() MyStringIReadOnlyListT = new string[] { "Hello" }; MyStringToStringDict = new Dictionary { { "key", "value" } }; + MyStringToStringIDict = new Dictionary { { "key", "value" } }; + MyStringToStringIReadOnlyDict = new Dictionary { { "key", "value" } }; } public void Verify() @@ -204,6 +210,8 @@ public void Verify() Assert.Equal("Hello", MyStringIReadOnlyListT[0]); Assert.Equal("value", MyStringToStringDict["key"]); + Assert.Equal("value", MyStringToStringIDict["key"]); + Assert.Equal("value", MyStringToStringIReadOnlyDict["key"]); } } } diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs index ff57dc66d5f4..1a0ae4d73d66 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs @@ -51,6 +51,8 @@ public class SimpleTestClassWithObject : ITestClass public object MyStringIReadOnlyCollectionT { get; set; } public object MyStringIReadOnlyListT { get; set; } public object MyStringToStringDict { get; set; } + public object MyStringToStringIDict { get; set; } + public object MyStringToStringIReadOnlyDict { get; set; } public static readonly string s_json = @"{" + @@ -94,7 +96,9 @@ public class SimpleTestClassWithObject : ITestClass @"""MyStringICollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyListT"" : [""Hello""]," + - @"""MyStringToStringDict"" : {""key"" : ""value""}" + + @"""MyStringToStringDict"" : {""key"" : ""value""}," + + @"""MyStringToStringIDict"" : {""key"" : ""value""}," + + @"""MyStringToStringIReadOnlyDict"" : {""key"" : ""value""}" + @"}"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); @@ -145,6 +149,8 @@ public void Initialize() MyStringIReadOnlyListT = new string[] { "Hello" }; MyStringToStringDict = new Dictionary { { "key", "value" } }; + MyStringToStringIDict = new Dictionary { { "key", "value" } }; + MyStringToStringIReadOnlyDict = new Dictionary { { "key", "value" } }; } public void Verify() @@ -193,6 +199,8 @@ public void Verify() Assert.Equal("Hello", ((IReadOnlyList)MyStringIReadOnlyListT)[0]); Assert.Equal("value", ((Dictionary)MyStringToStringDict)["key"]); + Assert.Equal("value", ((IDictionary)MyStringToStringIDict)["key"]); + Assert.Equal("value", ((IReadOnlyDictionary)MyStringToStringIReadOnlyDict)["key"]); } } } diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.cs b/src/System.Text.Json/tests/Serialization/TestClasses.cs index f6c7195ffe9a..826e1d478a31 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.cs @@ -587,6 +587,173 @@ public void Verify() } } + public class TestClassWithStringToPrimitiveDictionary : ITestClass + { + public Dictionary MyInt32Dict { get; set; } + public Dictionary MyBooleanDict { get; set; } + public Dictionary MySingleDict { get; set; } + public Dictionary MyDoubleDict { get; set; } + public Dictionary MyDateTimeDict { get; set; } + public IDictionary MyInt32IDict { get; set; } + public IDictionary MyBooleanIDict { get; set; } + public IDictionary MySingleIDict { get; set; } + public IDictionary MyDoubleIDict { get; set; } + public IDictionary MyDateTimeIDict { get; set; } + public IReadOnlyDictionary MyInt32IReadOnlyDict { get; set; } + public IReadOnlyDictionary MyBooleanIReadOnlyDict { get; set; } + public IReadOnlyDictionary MySingleIReadOnlyDict { get; set; } + public IReadOnlyDictionary MyDoubleIReadOnlyDict { get; set; } + public IReadOnlyDictionary MyDateTimeIReadOnlyDict { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyInt32Dict"":{" + + @"""key1"": 1," + + @"""key2"": 2" + + @"}," + + @"""MyBooleanDict"":{" + + @"""key1"": true," + + @"""key2"": false" + + @"}," + + @"""MySingleDict"":{" + + @"""key1"": 1.1," + + @"""key2"": 2.2" + + @"}," + + @"""MyDoubleDict"":{" + + @"""key1"": 3.3," + + @"""key2"": 4.4" + + @"}," + + @"""MyDateTimeDict"":{" + + @"""key1"": ""2019-01-30T12:01:02.0000000""," + + @"""key2"": ""2019-01-30T12:01:02.0000000Z""" + + @"}," + + @"""MyInt32IDict"":{" + + @"""key1"": 1," + + @"""key2"": 2" + + @"}," + + @"""MyBooleanIDict"":{" + + @"""key1"": true," + + @"""key2"": false" + + @"}," + + @"""MySingleIDict"":{" + + @"""key1"": 1.1," + + @"""key2"": 2.2" + + @"}," + + @"""MyDoubleIDict"":{" + + @"""key1"": 3.3," + + @"""key2"": 4.4" + + @"}," + + @"""MyDateTimeIDict"":{" + + @"""key1"": ""2019-01-30T12:01:02.0000000""," + + @"""key2"": ""2019-01-30T12:01:02.0000000Z""" + + @"}," + + @"""MyInt32IReadOnlyDict"":{" + + @"""key1"": 1," + + @"""key2"": 2" + + @"}," + + @"""MyBooleanIReadOnlyDict"":{" + + @"""key1"": true," + + @"""key2"": false" + + @"}," + + @"""MySingleIReadOnlyDict"":{" + + @"""key1"": 1.1," + + @"""key2"": 2.2" + + @"}," + + @"""MyDoubleIReadOnlyDict"":{" + + @"""key1"": 3.3," + + @"""key2"": 4.4" + + @"}," + + @"""MyDateTimeIReadOnlyDict"":{" + + @"""key1"": ""2019-01-30T12:01:02.0000000""," + + @"""key2"": ""2019-01-30T12:01:02.0000000Z""" + + @"}" + + @"}"); + + public void Initialize() + { + MyInt32Dict = new Dictionary { { "key1", 1 }, { "key2", 2 } }; + MyBooleanDict = new Dictionary { { "key1", true }, { "key2", false } }; + MySingleDict = new Dictionary { { "key1", 1.1f }, { "key2", 2.2f } }; + MyDoubleDict = new Dictionary { { "key1", 3.3d }, { "key2", 4.4d } }; + MyDateTimeDict = new Dictionary { { "key1", new DateTime(2019, 1, 30, 12, 1, 2) }, { "key2", new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) } }; + + MyInt32IDict = new Dictionary { { "key1", 1 }, { "key2", 2 } }; + MyBooleanIDict = new Dictionary { { "key1", true }, { "key2", false } }; + MySingleIDict = new Dictionary { { "key1", 1.1f }, { "key2", 2.2f } }; + MyDoubleIDict = new Dictionary { { "key1", 3.3d }, { "key2", 4.4d } }; + MyDateTimeIDict = new Dictionary { { "key1", new DateTime(2019, 1, 30, 12, 1, 2) }, { "key2", new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) } }; + + MyInt32IReadOnlyDict = new Dictionary { { "key1", 1 }, { "key2", 2 } }; + MyBooleanIReadOnlyDict = new Dictionary { { "key1", true }, { "key2", false } }; + MySingleIReadOnlyDict = new Dictionary { { "key1", 1.1f }, { "key2", 2.2f } }; + MyDoubleIReadOnlyDict = new Dictionary { { "key1", 3.3d }, { "key2", 4.4d } }; + MyDateTimeIReadOnlyDict = new Dictionary { { "key1", new DateTime(2019, 1, 30, 12, 1, 2) }, { "key2", new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) } }; + } + + public void Verify() + { + Assert.Equal(1, MyInt32Dict["key1"]); + Assert.Equal(2, MyInt32Dict["key2"]); + Assert.Equal(2, MyInt32Dict.Count); + + Assert.Equal(true, MyBooleanDict["key1"]); + Assert.Equal(false, MyBooleanDict["key2"]); + Assert.Equal(2, MyBooleanDict.Count); + + Assert.Equal(1.1f, MySingleDict["key1"]); + Assert.Equal(2.2f, MySingleDict["key2"]); + Assert.Equal(2, MySingleDict.Count); + + Assert.Equal(3.3d, MyDoubleDict["key1"]); + Assert.Equal(4.4d, MyDoubleDict["key2"]); + Assert.Equal(2, MyDoubleDict.Count); + + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2), MyDateTimeDict["key1"]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeDict["key2"]); + Assert.Equal(2, MyDateTimeDict.Count); + + Assert.Equal(1, MyInt32IDict["key1"]); + Assert.Equal(2, MyInt32IDict["key2"]); + Assert.Equal(2, MyInt32IDict.Count); + + Assert.Equal(true, MyBooleanIDict["key1"]); + Assert.Equal(false, MyBooleanIDict["key2"]); + Assert.Equal(2, MyBooleanIDict.Count); + + Assert.Equal(1.1f, MySingleIDict["key1"]); + Assert.Equal(2.2f, MySingleIDict["key2"]); + Assert.Equal(2, MySingleIDict.Count); + + Assert.Equal(3.3d, MyDoubleIDict["key1"]); + Assert.Equal(4.4d, MyDoubleIDict["key2"]); + Assert.Equal(2, MyDoubleIDict.Count); + + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2), MyDateTimeIDict["key1"]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeIDict["key2"]); + Assert.Equal(2, MyDateTimeIDict.Count); + + Assert.Equal(1, MyInt32IReadOnlyDict["key1"]); + Assert.Equal(2, MyInt32IReadOnlyDict["key2"]); + Assert.Equal(2, MyInt32IReadOnlyDict.Count); + + Assert.Equal(true, MyBooleanIReadOnlyDict["key1"]); + Assert.Equal(false, MyBooleanIReadOnlyDict["key2"]); + Assert.Equal(2, MyBooleanIReadOnlyDict.Count); + + Assert.Equal(1.1f, MySingleIReadOnlyDict["key1"]); + Assert.Equal(2.2f, MySingleIReadOnlyDict["key2"]); + Assert.Equal(2, MySingleIReadOnlyDict.Count); + + Assert.Equal(3.3d, MyDoubleIReadOnlyDict["key1"]); + Assert.Equal(4.4d, MyDoubleIReadOnlyDict["key2"]); + Assert.Equal(2, MyDoubleIReadOnlyDict.Count); + + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2), MyDateTimeIReadOnlyDict["key1"]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeIReadOnlyDict["key2"]); + Assert.Equal(2, MyDateTimeIReadOnlyDict.Count); + } + } + public class SimpleDerivedTestClass : SimpleTestClass { }