using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class DictionaryTupleConverter<T1, T2, TValue> : JsonConverter<Dictionary<(T1, T2), TValue>>
readonly Tuple2Converter<T1, T2> tupleConverter = new ();
public override Dictionary<(T1, T2), TValue>? ReadJson(JsonReader reader, Type objectType, Dictionary<(T1, T2), TValue>? existingValue, bool hasExistingValue, JsonSerializer serializer)
var innerDictionary = serializer.Deserialize<Dictionary<string, TValue>>(reader);
if (innerDictionary == null)
var dictionary = existingValue ?? (Dictionary<(T1, T2), TValue>)serializer.ContractResolver.ResolveContract(objectType)!.DefaultCreator()!;
foreach (var pair in innerDictionary)
dictionary.Add(((T1, T2))tupleConverter.ConvertFromInvariantString(pair.Key)!, pair.Value);
public override void WriteJson(JsonWriter writer, Dictionary<(T1, T2), TValue>? value, JsonSerializer serializer) =>
serializer.Serialize(writer, value!.ToDictionary(p => tupleConverter.ConvertToInvariantString(p.Key)!, p => p.Value));
public class Tuple2Converter<T1, T2> : TypeConverter {
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) {
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) {
var parts = Convert.ToString(value)!.Trim('(').Trim(')').Split(",");
var item1 = (T1)TypeDescriptor.GetConverter(typeof(T1)).ConvertFromInvariantString(parts[0].Trim())!;
var item2 = (T2)TypeDescriptor.GetConverter(typeof(T2)).ConvertFromInvariantString(parts[1].Trim())!;
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
if (destinationType == typeof(string) && value is ValueTuple<T1, T2> tuple)
var s1 = TypeDescriptor.GetConverter(typeof(T1)).ConvertToInvariantString(tuple.Item1)!;
var s2 = TypeDescriptor.GetConverter(typeof(T2)).ConvertToInvariantString(tuple.Item2)!;
return string.Format("({0},{1})", s1, s2);
return base.ConvertTo(context, culture, value, destinationType);
public static class TypeExtensions
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)
var types = type.GetDictionaryKeyValueTypes().ToList();
return types.Count == 1 ? types[0] : null;
public static IEnumerable<Type> BaseTypesAndSelf(this Type? type)
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
public string? Value { get; set; }
public static void Test()
var d = (from i in Enumerable.Range(0, n)
from j in Enumerable.Range(0, n)
select ((i, j), new MyClass { Value = (i+j).ToString() }))
.ToDictionary(p => p.Item1, p => p.Item2);
var settings = new JsonSerializerSettings
Converters = { new DictionaryTupleConverter<int, int, MyClass>() },
var json = JsonConvert.SerializeObject(d, Formatting.Indented, settings);
var d2 = JsonConvert.DeserializeAnonymousType(json, d, settings);
var json2 = JsonConvert.SerializeObject(d2, Formatting.Indented, settings);
Assert.AreEqual(json, json2);
Assert.AreEqual(d.Count(), d2!.Count());
public static partial class JsonExtensions
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected end of JSON stream.");
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Namespace, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");