{
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>);
}
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"]);
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"]);
--- /dev/null
+// 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);
+ }
+ }
+}
{
/// <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
{
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);
+ }
}
}
}
[Fact]
- public static void AllPrimitivesConvertion()
+ public static void AllPrimitivesConversion()
{
ClassWithDictionaries obj;
Guid guid = Guid.NewGuid();
<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>