using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
public class DictionaryConverterFactory : JsonConverterFactory
public override bool CanConvert(Type typeToConvert)
return typeToConvert.IsClass && typeToConvert.GetDictionaryKeyValueType() != null && typeToConvert.GetConstructor(Type.EmptyTypes) != null;
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
var keyValueTypes = typeToConvert.GetDictionaryKeyValueType();
var converterType = typeof(DictionaryAsArrayConverter<,,>).MakeGenericType(typeToConvert, keyValueTypes.Value.Key, keyValueTypes.Value.Value);
return (JsonConverter)Activator.CreateInstance(converterType);
public class DictionaryAsArrayConverter<TKey, TValue> : DictionaryAsArrayConverter<Dictionary<TKey, TValue>, TKey, TValue>
public class DictionaryAsArrayConverter<TDictionary, TKey, TValue> : JsonConverter<TDictionary> where TDictionary : class, IDictionary<TKey, TValue>, new()
public TKey Key { get; set; }
public TValue Value { get; set; }
public override TDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
var list = JsonSerializer.Deserialize<List<KeyValueDTO>>(ref reader, options);
var dictionary = typeToConvert == typeof(Dictionary<TKey, TValue>) ? (TDictionary)(object)new Dictionary<TKey, TValue>(list.Count) : new TDictionary();
foreach (var pair in list)
dictionary.Add(pair.Key, pair.Value);
public override void Write(Utf8JsonWriter writer, TDictionary value, JsonSerializerOptions options)
JsonSerializer.Serialize(writer, value.Select(p => new KeyValueDTO { Key = p.Key, Value = p.Value }), options);
public static class TypeExtensions
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
throw new ArgumentNullException();
return new[] { type }.Concat(type.GetInterfaces());
return type.GetInterfaces();
public static KeyValuePair<Type, Type>? GetDictionaryKeyValueType(this Type type)
KeyValuePair<Type, Type>? types = null;
foreach (var pair in type.GetDictionaryKeyValueTypes())
public static IEnumerable<KeyValuePair<Type, Type>> GetDictionaryKeyValueTypes(this Type type)
foreach (Type intType in type.GetInterfacesAndSelf())
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
var args = intType.GetGenericArguments();
yield return new KeyValuePair<Type, Type>(args[0], args[1]);
public string Value { get; set; }
public class KeyValuePairDTO<TKey, TValue>
public TKey Key { get; set; }
public TValue Value { get; set; }
public class MyDictionary : Dictionary<string, string> { }
public class RootObject<T>
public T Dictionary { get; set; }
public static void Test()
var keys = new [] { "z", "a", "b", "c" };
var intkeys = new [] { 5, 4, 3, 2, 1 };
Test<Dictionary<string, string>, string, string>(keys.Select(s => (s, "value of s")));
Test<SortedDictionary<string, string>, string, string>(keys.Select(s => (s, "value of s")));
Test<Dictionary<string, ValueClass>, string, ValueClass>(keys.Select(s => (s, new ValueClass { Value = "value of s" })));
Test<MyDictionary, string, string>(keys.Select(s => (s, "value of s")));
Test<Dictionary<string, Dictionary<string, string>>, string, Dictionary<string, string>>(keys.Select(s => (s, keys.ToDictionary(k => k, k => "value of " + k))));
Test<Dictionary<int, int>, int, int>(intkeys.Select(i => (i, i)));
static void TestIDictionaryProperty()
var keys = new [] { "z", "a", "b", "c" };
var root = new RootObject<IDictionary<string, string>>
Dictionary = keys.ToDictionary(s => s, s => "value of " + s),
var options = new JsonSerializerOptions
Converters = { new DictionaryConverterFactory() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
var json = JsonSerializer.Serialize(root, options);
var root2 = JsonSerializer.Deserialize<RootObject<IDictionary<string, string>>>(json, options);
var json2 = JsonSerializer.Serialize(root2, options);
var rootAsArray = JsonSerializer.Deserialize<RootObject<List<KeyValuePairDTO<string, string>>>>(json, options);
Assert.IsTrue(root.Dictionary.Count == rootAsArray.Dictionary.Count);
Assert.IsTrue(json == json2);
Console.WriteLine("\nSerialized {0}:", root);
static void Test<TDictionary, TKey, TValue>(IEnumerable<(TKey key, TValue value)> pairs) where TDictionary : class, IDictionary<TKey, TValue>, new()
var dictionary = new TDictionary();
foreach (var pair in pairs)
dictionary.Add(pair.key, pair.value);
var options = new JsonSerializerOptions
Converters = { new DictionaryConverterFactory() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
var json = JsonSerializer.Serialize(dictionary, options);
var dictionary2 = JsonSerializer.Deserialize<TDictionary>(json, options);
var json2 = JsonSerializer.Serialize(dictionary2, options);
Assert.IsTrue(dictionary.Count == dictionary2.Count);
var list = JsonSerializer.Deserialize<List<KeyValuePairDTO<TKey, TValue>>>(json, options);
Assert.IsTrue(list.Count == dictionary2.Count);
Assert.IsTrue(list.Select(i => i.Key).ToHashSet().SetEquals(dictionary.Keys));
Assert.IsTrue(dictionary2.Keys.ToHashSet().SetEquals(dictionary.Keys));
Console.WriteLine("\nSerialized {0}:", dictionary);
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];