I am attempting to serialize/deserialize the following class to and from Json using Json.Net:
public class ChildDictionary:Dictionary
Because your dictionary has both a complex key and additional properties, you will need to use a custom JsonConverter
to serialize and deserialize this class. Below is a converter that should do the job. It handles the serialization in two parts: first it uses reflection to deal with any read-write properties on the class, then it casts the object to a dictionary interface to handle the key-value pairs. The latter are written to the JSON as an array of objects with Key
and Value
properties so that the complex keys are managed without needing to jump through extra hoops.
public class ComplexDictionaryConverter<K,V> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<K,V>)));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject obj = new JObject();
foreach (PropertyInfo prop in GetReadWriteProperties(value.GetType()))
{
object val = prop.GetValue(value);
obj.Add(prop.Name, val != null ? JToken.FromObject(val, serializer) : new JValue(val));
}
JArray array = new JArray();
foreach (var kvp in (IDictionary<K, V>)value)
{
JObject item = new JObject();
item.Add("Key", JToken.FromObject(kvp.Key, serializer));
item.Add("Value", kvp.Value != null ? JToken.FromObject(kvp.Value, serializer) : new JValue(kvp.Value));
array.Add(item);
}
obj.Add("KVPs", array);
obj.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
IDictionary<K, V> dict = (IDictionary<K, V>)Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in GetReadWriteProperties(objectType))
{
JToken token = obj[prop.Name];
object val = token != null ? token.ToObject(prop.PropertyType, serializer) : null;
prop.SetValue(dict, val);
}
JArray array = (JArray)obj["KVPs"];
foreach (JObject kvp in array.Children<JObject>())
{
K key = kvp["Key"].ToObject<K>(serializer);
V val = kvp["Value"].ToObject<V>(serializer);
dict.Add(key, val);
}
return dict;
}
private IEnumerable<PropertyInfo> GetReadWriteProperties(Type type)
{
return type.GetProperties().Where(p => p.CanRead && p.CanWrite && !p.GetIndexParameters().Any());
}
}
To use the converter, you can mark your class with a [JsonConverter]
attribute like this (be sure the generic parameters match those of the dictionary your class inherits from):
[JsonConverter(typeof(ComplexDictionaryConverter<Employee, double>))]
public class ChildDictionary : Dictionary<Employee, double>
{
...
}
Here is a demo showing a full round-trip:
class Program
{
static void Main(string[] args)
{
ChildDictionary dict = new ChildDictionary();
dict.Name = "Roster";
dict.Add(new Employee { Id = 22, Name = "Joe", HireDate = new DateTime(2012, 4, 17) }, 1923.07);
dict.Add(new Employee { Id = 45, Name = "Fred", HireDate = new DateTime(2010, 8, 22) }, 1415.25);
string json = JsonConvert.SerializeObject(dict, Formatting.Indented);
Console.WriteLine(json);
dict = JsonConvert.DeserializeObject<ChildDictionary>(json);
Console.WriteLine("Name: " + dict.Name);
foreach (var kvp in dict)
{
Console.WriteLine("Employee Id: " + kvp.Key.Id);
Console.WriteLine("Employee Name: " + kvp.Key.Name);
Console.WriteLine("Employee Hire Date: " + kvp.Key.HireDate);
Console.WriteLine("Amount: " + kvp.Value);
Console.WriteLine();
}
}
}
[JsonConverter(typeof(ComplexDictionaryConverter<Employee, double>))]
public class ChildDictionary : Dictionary<Employee, double>
{
public string Name { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime HireDate { get; set; }
}
Output:
{
"Name": "Roster",
"KVPs": [
{
"Key": {
"Id": 22,
"Name": "Joe",
"HireDate": "2012-04-17T00:00:00"
},
"Value": 1923.07
},
{
"Key": {
"Id": 45,
"Name": "Fred",
"HireDate": "2010-08-22T00:00:00"
},
"Value": 1415.25
}
]
}
Name: Roster
Employee Id: 22
Employee Name: Joe
Employee Hire Date: 4/17/2012 12:00:00 AM
Amount: 1923.07
Employee Id: 45
Employee Name: Fred
Employee Hire Date: 8/22/2010 12:00:00 AM
Amount: 1415.25
Fiddle: https://dotnetfiddle.net/fTfoIk