using System.Collections;
using System.Collections.Generic;
using System.Text.Json.Serialization;
public class JsonDictionaryConverterFactory : JsonConverterFactory
public override bool CanConvert(Type typeToConvert) => TryGetDictionaryType(typeToConvert, out _, out _, out _);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
TryGetDictionaryType(typeToConvert, out var dictionaryType, out var keyType, out var valueType);
var converterType = typeof(JsonDictionaryConverter<,,>).MakeGenericType(dictionaryType, keyType, valueType);
return (JsonConverter)Activator.CreateInstance(converterType, options, this);
static bool TryGetDictionaryType(Type typeToConvert, out Type dictionaryType, out Type keyType, out Type valueType)
var keyValueTypes = typeToConvert.GetDictionaryKeyValueType();
if (keyValueTypes != null && keyValueTypes.Length == 2)
if (typeToConvert.IsInterface)
var concreteType = typeof(Dictionary<,>).MakeGenericType(keyValueTypes);
if (typeToConvert.IsAssignableFrom(concreteType))
dictionaryType = concreteType;
keyType = keyValueTypes[0];
valueType = keyValueTypes[1];
if (typeToConvert.GetConstructor(Type.EmptyTypes) != null)
dictionaryType = typeToConvert;
keyType = keyValueTypes[0];
valueType = keyValueTypes[1];
dictionaryType = keyType = valueType = null;
class JsonDictionaryConverter<TDictionary, TKey, TValue> : JsonConverter<TDictionary> where TDictionary : IDictionary<TKey, TValue>, new()
readonly JsonSerializerOptions modifiedOptions;
public JsonDictionaryConverter(JsonSerializerOptions options, JsonDictionaryConverterFactory factory) => this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
public override void Write(Utf8JsonWriter writer, TDictionary value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, modifiedOptions);
public override TDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
switch (reader.TokenType)
case JsonTokenType.StartObject:
return (TDictionary)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);
case JsonTokenType.StartArray:
var list = JsonSerializer.Deserialize<List<KeyValuePair<TKey, TValue>>>(ref reader, modifiedOptions);
var dictionary = new TDictionary();
foreach (var item in list)
throw new JsonException();
public static class JsonExtensions
public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
var copy = new JsonSerializerOptions(options);
for (var i = copy.Converters.Count - 1; i >= 0; i--)
if (copy.Converters[i].GetType() == converterType)
copy.Converters.RemoveAt(i);
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
=> (type ?? throw new ArgumentNullException()).IsInterface ? new[] { type }.Concat(type.GetInterfaces()) : type.GetInterfaces();
public static IEnumerable<Type []> GetDictionaryKeyValueTypes(this Type type)
=> type.GetInterfacesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>)).Select(t => t.GetGenericArguments());
public static Type [] GetDictionaryKeyValueType(this Type type)
=> type.GetDictionaryKeyValueTypes().SingleOrDefaultIfMultiple();
public static TSource SingleOrDefaultIfMultiple<TSource>(this IEnumerable<TSource> source)
var elements = source.Take(2).ToArray();
return (elements.Length == 1) ? elements[0] : default(TSource);
public static void Test()
foreach (var json in GetJson())
var options = new JsonSerializerOptions
Converters = { new JsonDictionaryConverterFactory() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
var dictionary = JsonSerializer.Deserialize<IDictionary<string, long>>(json, options);
var newJson = JsonSerializer.Serialize(dictionary, options);
Console.WriteLine("Deserializing JSON:");
Console.WriteLine("Re-serialized {0}:", dictionary);
Console.WriteLine(newJson);
static IEnumerable<string> GetJson() =>
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version);
Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");