json.net: specify converter for dictionary keys
I have a JSON:
{
"data": { "A": 5, "B": 6 },
"foo": "foo",
"bar": "bar"
}
I need to deserialize data into a class:
public Dictionary<MyEnum, int> Data { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
But MyEnum values are CodeA
, and CodeB
instead of simply A
and B
respectively.
I have a custom Converter that can handle conversion. But how do I specify a JsonConverter
to use with Dictionary keys?
Solution 1:
I believe the only way is to make a JsonConverter for the whole Dictionary<MyEnum, int>
type, or Dictionary<MyEnum, T>
.
Dictionary keys are not regarded as values and will not be run through the JsonConverters. TypeConverters would have been a solution, but the default string to enum conversion will enter before it looks at the TypeConverters.
So... I don't think it can be done any other way.
EDIT:
Not fully tested, but I use something like this in a project of mine:
public class DictionaryWithSpecialEnumKeyConverter : JsonConverter
{
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var valueType = objectType.GetGenericArguments()[1];
var intermediateDictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), valueType);
var intermediateDictionary = (IDictionary)Activator.CreateInstance(intermediateDictionaryType);
serializer.Populate(reader, intermediateDictionary);
var finalDictionary = (IDictionary)Activator.CreateInstance(objectType);
foreach (DictionaryEntry pair in intermediateDictionary)
finalDictionary.Add(Enum.Parse(MyEnum, "Code" + pair.Key, false), pair.Value);
return finalDictionary;
}
public override bool CanConvert(Type objectType)
{
return objectType.IsA(typeof(IDictionary<,>)) &&
objectType.GetGenericArguments()[0].IsA<MyEnum>();
}
}
You will need this little helper:
public static bool IsA(this Type type, Type typeToBe)
{
if (!typeToBe.IsGenericTypeDefinition)
return typeToBe.IsAssignableFrom(type);
var toCheckTypes = new List<Type> { type };
if (typeToBe.IsInterface)
toCheckTypes.AddRange(type.GetInterfaces());
var basedOn = type;
while (basedOn.BaseType != null)
{
toCheckTypes.Add(basedOn.BaseType);
basedOn = basedOn.BaseType;
}
return toCheckTypes.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeToBe);
}
Hope it works out for you.