Add Dictionary<Enum, TValue> converter example with JSON syntax of object+property...
authorSteve Harter <steveharter@users.noreply.github.com>
Thu, 10 Oct 2019 19:39:59 +0000 (14:39 -0500)
committerGitHub <noreply@github.com>
Thu, 10 Oct 2019 19:39:59 +0000 (14:39 -0500)
Commit migrated from https://github.com/dotnet/corefx/commit/5b43eb32b5048f35baea22bf4ef6f4736b33da5f

src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.ContravariantDictionaryConverter.cs [moved from src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryConverterForIDictionary.cs with 88% similarity]
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryEnumConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryGuidConverter.cs
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryInt32StringKeyValueConverter.cs
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryKeyValueConverter.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

@@ -9,21 +9,19 @@ namespace System.Text.Json.Serialization.Tests
 {
     public static partial class CustomConverterTests
     {
-        /// <summary>
-        /// Demonstrates custom <see cref="Dictionary{string, long}">.
-        /// Adds offset to each value to verify the converter ran.
-        /// </summary>
-        private class DictionaryConverterForIDictionary : JsonConverter<IDictionary<string, long>>
+        // Test class for a contravariant converter (IDictionary->Dictionary).
+        private class ContravariantDictionaryConverter : JsonConverter<IDictionary<string, long>>
         {
             private long _offset;
 
-            public DictionaryConverterForIDictionary(long offset)
+            public ContravariantDictionaryConverter(long offset)
             {
                 _offset = offset;
             }
 
             public override bool CanConvert(Type typeToConvert)
             {
+                // For simplicity, just support Dictionary not IDictionary.
                 return typeToConvert == typeof(Dictionary<string, long>);
             }
 
@@ -83,7 +81,7 @@ namespace System.Text.Json.Serialization.Tests
             const string Json = @"{""Key1"":1,""Key2"":2}";
 
             var options = new JsonSerializerOptions();
-            options.Converters.Add(new DictionaryConverterForIDictionary(10));
+            options.Converters.Add(new ContravariantDictionaryConverter(10));
 
             Dictionary<string, long> dictionary = JsonSerializer.Deserialize<Dictionary<string, long>>(Json, options);
             Assert.Equal(11, dictionary["Key1"]);
@@ -98,7 +96,7 @@ namespace System.Text.Json.Serialization.Tests
             const string Json = @"{""MyInt"":32,""MyDictionary"":{""Key1"":1,""Key2"":2},""MyString"":""Hello""}";
 
             var options = new JsonSerializerOptions();
-            options.Converters.Add(new DictionaryConverterForIDictionary(10));
+            options.Converters.Add(new ContravariantDictionaryConverter(10));
 
             ClassHavingDictionaryFieldWhichUsesCustomConverter dictionary = JsonSerializer.Deserialize<ClassHavingDictionaryFieldWhichUsesCustomConverter>(Json, options);
             Assert.Equal(11, dictionary.MyDictionary["Key1"]);
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryEnumConverter.cs b/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DictionaryEnumConverter.cs
new file mode 100644 (file)
index 0000000..c633d34
--- /dev/null
@@ -0,0 +1,206 @@
+// 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 System.Diagnostics;
+using System.Reflection;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class CustomConverterTests
+    {
+        /// <summary>
+        /// Demonstrates a <see cref="Dictionary{TKey, TValue}"> converter where TKey is an <see cref="Enum"/> with the string
+        /// version of the Enum is used.
+        /// Sample JSON for <see cref="Dictionary{MyEnum, object}">: {"MyEnumValue":{"MyProperty":"myValue"}}
+        /// Sample JSON for <see cref="Dictionary{MyEnum, int}">: {"MyEnumValue":42}
+        /// </summary>
+        /// <remarks>
+        /// A case-insensitive parse is performed for the Enum value.
+        /// A <see cref="JsonException"/> is thrown when deserializing if the Enum value is not found.
+        /// </remarks>
+        internal sealed class DictionaryEnumConverter : JsonConverterFactory
+        {
+            public override bool CanConvert(Type typeToConvert)
+            {
+                if (!typeToConvert.IsGenericType)
+                {
+                    return false;
+                }
+
+                if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
+                {
+                    return false;
+                }
+
+                return typeToConvert.GetGenericArguments()[0].IsEnum;
+            }
+
+            public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
+            {
+                Type keyType = type.GetGenericArguments()[0];
+                Type valueType = type.GetGenericArguments()[1];
+
+                JsonConverter converter = (JsonConverter)Activator.CreateInstance(
+                    typeof(DictionaryEnumConverterInner<,>).MakeGenericType(new Type[] { keyType, valueType }),
+                    BindingFlags.Instance | BindingFlags.Public,
+                    binder: null,
+                    args: new object[] { options },
+                    culture: null);
+
+                return converter;
+            }
+
+            private class DictionaryEnumConverterInner<TKey, TValue> : JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
+            {
+                private readonly JsonConverter<TValue> _valueConverter;
+                private Type _keyType;
+                private Type _valueType;
+
+                public DictionaryEnumConverterInner(JsonSerializerOptions options)
+                {
+                    // For performance, use the existing converter if available.
+                    _valueConverter = (JsonConverter<TValue>)options.GetConverter(typeof(TValue));
+
+                    // Cache the key and value types.
+                    _keyType = typeof(TKey);
+                    _valueType = typeof(TValue);
+                }
+
+                public override Dictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+                {
+                    if (reader.TokenType != JsonTokenType.StartObject)
+                    {
+                        throw new JsonException();
+                    }
+
+                    Dictionary<TKey, TValue> value = new Dictionary<TKey, TValue>();
+
+                    while (reader.Read())
+                    {
+                        if (reader.TokenType == JsonTokenType.EndObject)
+                        {
+                            return value;
+                        }
+
+                        // Get the key.
+                        if (reader.TokenType != JsonTokenType.PropertyName)
+                        {
+                            throw new JsonException();
+                        }
+
+                        string propertyName = reader.GetString();
+
+                        // For performance, parse with ignoreCase:false first.
+                        if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
+                            !Enum.TryParse(propertyName, ignoreCase: true, out key))
+                        {
+                            throw new JsonException($"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
+                        }
+
+                        // Get the value.
+                        TValue v;
+                        if (_valueConverter != null)
+                        {
+                            reader.Read();
+                            v = _valueConverter.Read(ref reader, _valueType, options);
+                        }
+                        else
+                        {
+                            v = JsonSerializer.Deserialize<TValue>(ref reader, options);
+                        }
+
+                        // Add to dictionary.
+                        value.Add(key, v);
+                    }
+
+                    throw new JsonException();
+                }
+
+                public override void Write(Utf8JsonWriter writer, Dictionary<TKey, TValue> value, JsonSerializerOptions options)
+                {
+                    writer.WriteStartObject();
+
+                    foreach (KeyValuePair<TKey, TValue> kvp in value)
+                    {
+                        writer.WritePropertyName(kvp.Key.ToString());
+
+                        if (_valueConverter != null)
+                        {
+                            _valueConverter.Write(writer, kvp.Value, options);
+                        }
+                        else
+                        {
+                            JsonSerializer.Serialize(writer, kvp.Value, options);
+                        }
+                    }
+
+                    writer.WriteEndObject();
+                }
+            }
+        }
+
+        [Fact]
+        public static void VerifyDictionaryEnumToIntConverter()
+        {
+            const string Json = @"{""One"":1}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new DictionaryEnumConverter());
+
+            Dictionary<MyEnum, int> obj = JsonSerializer.Deserialize<Dictionary<MyEnum, int>>(Json, options);
+            Assert.Equal(1, obj.Count);
+            Assert.Equal(1, obj[MyEnum.One]);
+
+            string jsonRoundTripped = JsonSerializer.Serialize(obj, options);
+            Assert.Equal(Json, jsonRoundTripped);
+        }
+
+        [Fact]
+        public static void VerifyDictionaryEnumToIntConverterCaseInsensitive()
+        {
+            const string Json = @"{""one"":1}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new DictionaryEnumConverter());
+
+            Dictionary<MyEnum, int> obj = JsonSerializer.Deserialize<Dictionary<MyEnum, int>>(Json, options);
+            Assert.Equal(1, obj.Count);
+            Assert.Equal(1, obj[MyEnum.One]);
+
+            // The serialized JSON is cased per the enum's actual vales.
+            string jsonRoundTripped = JsonSerializer.Serialize(obj, options);
+            Assert.Equal(@"{""One"":1}", jsonRoundTripped);
+        }
+
+        [Fact]
+        public static void VerifyDictionaryEnumToObjectConverter()
+        {
+            const string Json = @"{""One"":{""Value"":""test""}}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new DictionaryEnumConverter());
+
+            Dictionary<MyEnum, Entity> obj = JsonSerializer.Deserialize<Dictionary<MyEnum, Entity>>(Json, options);
+            Assert.Equal(1, obj.Count);
+            Assert.Equal("test", obj[MyEnum.One].Value);
+
+            string jsonRoundTripped = JsonSerializer.Serialize(obj, options);
+            Assert.Equal(Json, jsonRoundTripped);
+        }
+
+        [Fact]
+        public static void VerifyDictionaryEnumConverterFail()
+        {
+            const string Json = @"{""BAD"":2}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new DictionaryEnumConverter());
+
+            JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Dictionary<MyEnum, int>>(Json, options));
+            Assert.Contains($"Unable to convert \"BAD\" to Enum \"{typeof(MyEnum)}\".", ex.Message);
+        }
+    }
+}
index f085da7..ef0d10f 100644 (file)
@@ -12,7 +12,8 @@ namespace System.Text.Json.Serialization.Tests
     {
         /// <summary>
         /// Demonstrates a <see cref="Dictionary{Guid, TValue}"> converter using a JSON object with property names representing keys.
-        /// Sample JSON for <see cref="Dictionary{Guid, object}">: {"2E6E1787-1874-49BF-91F1-0F65CCB6C161":{}}
+        /// Sample JSON for <see cref="Dictionary{Guid, object}">: {"2E6E1787-1874-49BF-91F1-0F65CCB6C161":{"MyProperty":"myValue"}}
+        /// Sample JSON for <see cref="Dictionary{Guid, int}">: {"2E6E1787-1874-49BF-91F1-0F65CCB6C161":42}
         /// </summary>
         internal sealed class DictionaryGuidConverter : JsonConverterFactory
         {
index f057360..bfdf8a3 100644 (file)
@@ -4,82 +4,84 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Reflection;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
 {
-    /// <summary>
-    /// Demonstrates a <see cref="Dictionary{int, string}"> converter using a JSON array containing KeyValuePair objects.
-    /// Sample JSON: [{"Key":1,"Value":"One"},{"Key":2,"Value":"Two"}]
-    /// </summary>
-    internal class DictionaryInt32StringKeyValueConverter : JsonConverter<Dictionary<int, string>>
+    public static partial class CustomConverterTests
     {
-        private JsonConverter<KeyValuePair<int, string>> _intToStringConverter;
-
-        public DictionaryInt32StringKeyValueConverter(JsonSerializerOptions options)
+        /// <summary>
+        /// Demonstrates a <see cref="Dictionary{int, string}"> converter using a JSON array containing KeyValuePair objects.
+        /// Sample JSON: [{"Key":1,"Value":"One"},{"Key":2,"Value":"Two"}]
+        /// </summary>
+        internal class DictionaryInt32StringKeyValueConverter : JsonConverter<Dictionary<int, string>>
         {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
+            private JsonConverter<KeyValuePair<int, string>> _intToStringConverter;
 
-            _intToStringConverter = (JsonConverter<KeyValuePair<int, string>>)options.GetConverter(typeof(KeyValuePair<int, string>));
+            public DictionaryInt32StringKeyValueConverter(JsonSerializerOptions options)
+            {
+                if (options == null)
+                {
+                    throw new ArgumentNullException(nameof(options));
+                }
 
-            // KeyValuePair<> converter is built-in.
-            Debug.Assert(_intToStringConverter != null);
-        }
+                _intToStringConverter = (JsonConverter<KeyValuePair<int, string>>)options.GetConverter(typeof(KeyValuePair<int, string>));
 
-        public override Dictionary<int, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-        {
-            if (reader.TokenType != JsonTokenType.StartArray)
-            {
-                throw new JsonException();
+                // KeyValuePair<> converter is built-in.
+                Debug.Assert(_intToStringConverter != null);
             }
 
-            var value = new Dictionary<int, string>();
-
-            while (reader.Read())
+            public override Dictionary<int, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
             {
-                if (reader.TokenType == JsonTokenType.EndArray)
+                if (reader.TokenType != JsonTokenType.StartArray)
                 {
-                    return value;
+                    throw new JsonException();
                 }
 
-                KeyValuePair<int, string> kvpair = _intToStringConverter.Read(ref reader, typeToConvert, options);
+                var value = new Dictionary<int, string>();
 
-                value.Add(kvpair.Key, kvpair.Value);
-            }
+                while (reader.Read())
+                {
+                    if (reader.TokenType == JsonTokenType.EndArray)
+                    {
+                        return value;
+                    }
 
-            throw new JsonException();
-        }
+                    KeyValuePair<int, string> kvpair = _intToStringConverter.Read(ref reader, typeToConvert, options);
 
-        public override void Write(Utf8JsonWriter writer, Dictionary<int, string> value, JsonSerializerOptions options)
-        {
-            writer.WriteStartArray();
+                    value.Add(kvpair.Key, kvpair.Value);
+                }
 
-            foreach (KeyValuePair<int, string> item in value)
-            {
-                _intToStringConverter.Write(writer, item, options);
+                throw new JsonException();
             }
 
-            writer.WriteEndArray();
+            public override void Write(Utf8JsonWriter writer, Dictionary<int, string> value, JsonSerializerOptions options)
+            {
+                writer.WriteStartArray();
+
+                foreach (KeyValuePair<int, string> item in value)
+                {
+                    _intToStringConverter.Write(writer, item, options);
+                }
+
+                writer.WriteEndArray();
+            }
         }
-    }
 
-    [Fact]
-    public static void Int32StringKeyValueArrayConverter()
-    {
-        const string json = @"[{""Key"":1,""Value"":""ValueOne""},{""Key"":2,""Value"":""ValueTwo""}]";
+        [Fact]
+        public static void VerifyDictionaryInt32StringKeyValueConverter()
+        {
+            const string json = @"[{""Key"":1,""Value"":""ValueOne""},{""Key"":2,""Value"":""ValueTwo""}]";
 
-        var options = new JsonSerializerOptions();
-        options.Converters.Add(new DictionaryInt32StringKeyValueConverter(options));
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new DictionaryInt32StringKeyValueConverter(options));
 
-        Dictionary<int, string> dictionary = JsonSerializer.Deserialize<Dictionary<int, string>>(json, options);
-        Assert.Equal("ValueOne", dictionary[1]);
-        Assert.Equal("ValueTwo", dictionary[2]);
+            Dictionary<int, string> dictionary = JsonSerializer.Deserialize<Dictionary<int, string>>(json, options);
+            Assert.Equal("ValueOne", dictionary[1]);
+            Assert.Equal("ValueTwo", dictionary[2]);
 
-        string jsonSerialized = JsonSerializer.Serialize(dictionary, options);
-        Assert.Equal(json, jsonSerialized);
+            string jsonSerialized = JsonSerializer.Serialize(dictionary, options);
+            Assert.Equal(json, jsonSerialized);
+        }
     }
 }
index 5873eb6..c245d7d 100644 (file)
@@ -157,7 +157,7 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-        public static void AllPrimitivesConvertion()
+        public static void AllPrimitivesConversion()
         {
             ClassWithDictionaries obj;
             Guid guid = Guid.NewGuid();
index 5e81a9e..c31a729 100644 (file)
     <Compile Include="Serialization\CustomConverterTests.Attribute.cs" />
     <Compile Include="Serialization\CustomConverterTests.BadConverters.cs" />
     <Compile Include="Serialization\CustomConverterTests.cs" />
+    <Compile Include="Serialization\CustomConverterTests.ContravariantDictionaryConverter.cs" />
     <Compile Include="Serialization\CustomConverterTests.DerivedTypes.cs" />
-    <Compile Include="Serialization\CustomConverterTests.DictionaryConverterForIDictionary.cs" />
+    <Compile Include="Serialization\CustomConverterTests.DictionaryEnumConverter.cs" />
+    <Compile Include="Serialization\CustomConverterTests.DictionaryGuidConverter.cs" />
     <Compile Include="Serialization\CustomConverterTests.DictionaryInt32StringConverter.cs" />
+    <Compile Include="Serialization\CustomConverterTests.DictionaryInt32StringKeyValueConverter.cs" />
     <Compile Include="Serialization\CustomConverterTests.DictionaryKeyValueConverter.cs" />
-    <Compile Include="Serialization\CustomConverterTests.DictionaryGuidConverter.cs" />
     <Compile Include="Serialization\CustomConverterTests.Enum.cs" />
     <Compile Include="Serialization\CustomConverterTests.Exceptions.cs" />
     <Compile Include="Serialization\CustomConverterTests.Int32.cs" />
     <Compile Include="JsonObjectTests.cs" />
     <Compile Include="JsonStringTests.cs" />
   </ItemGroup>
-  <ItemGroup>
-    <None Include="Serialization\CustomConverterTests.DictionaryInt32StringKeyValueConverter.cs" />
-  </ItemGroup>
 </Project>