using System.Collections;
using System.Collections.Generic;
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 TypeConverter converter1 = TypeDescriptor.GetConverter(typeof(T1));
readonly TypeConverter converter2 = TypeDescriptor.GetConverter(typeof(T2));
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(ConvertFrom(pair.Key), pair.Value);
public override void WriteJson(JsonWriter writer, Dictionary<(T1, T2), TValue>? value, JsonSerializer serializer) =>
serializer.Serialize(writer, value!.ToDictionary(p => ConvertTo(p.Key)!, p => p.Value));
(T1, T2) ConvertFrom(string value)
var parts = value.Trim('(').Trim(')').Split(",");
var item1 = (T1)converter1.ConvertFromInvariantString(parts[0].Trim())!;
var item2 = (T2)converter2.ConvertFromInvariantString(parts[1].Trim())!;
string ConvertTo((T1, T2) value)
var s1 = converter1.ConvertToInvariantString(value.Item1)!;
var s2 = converter2.ConvertToInvariantString(value.Item2)!;
return string.Format("({0},{1})", s1, s2);
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 HexWithMeta { Value = (i+j).ToString() }))
.ToDictionary(p => p.Item1, p => p.Item2);
var settings = new JsonSerializerSettings
Converters = { new DictionaryTupleConverter<int, int, HexWithMeta>() },
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 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: ");