How can I serialize/deserialize a dictionary with custom keys using Json.Net?
This should do the trick:
Serialization:
JsonConvert.SerializeObject(expected.ToArray(), Formatting.Indented, jsonSerializerSettings);
By calling expected.ToArray()
you're serializing an array of KeyValuePair<MyClass, object>
objects rather than the dictionary.
Deserialization:
JsonConvert.DeserializeObject<KeyValuePair<IDataKey, object>[]>(output, jsonSerializerSettings).ToDictionary(kv => kv.Key, kv => kv.Value);
Here you deserialize the array and then retrieve the dictionary with .ToDictionary(...)
call.
I'm not sure if the output meets your expectations, but surely it passes the equality assertion.
Usage-specific serialization for complex type in Dictionary with JSON.Net
What you could do is serialize and deserialize your dictionary in a proxy KeyValuePair<string, string>
array, like so:
[DataContract]
public class MyContainer
{
public MyContainer() {
this.Dictionary = new Dictionary<MyValue, int>();
}
[DataMember]
public MyValue MyValue { get; set; }
[IgnoreDataMember]
public Dictionary<MyValue, int> Dictionary { get; set; }
[DataMember(Name="Dictionary")]
private KeyValuePair<MyValue, int> [] SerializedDictionary
{
get
{
if (Dictionary == null)
return null;
return Dictionary.ToArray();
}
set
{
if (value == null)
{
Dictionary = null;
}
else
{
Dictionary = value.ToDictionary(pair => pair.Key, pair => pair.Value);
}
}
}
}
(Here I'm using the DataContract
attributes, but I could just as easily have used [JsonIgnore]
and [JsonProperty("Dictionary")]
)
So, to test this (and assuming that you have properly overridden GetHashCode()
and Equals()
on MyValue
, which you need to do in order to use it as a dictionary key), I did the following:
public static class TestDictionaryJson
{
public static void Test()
{
var dict = new Dictionary<MyValue, int>();
dict[(new MyValue("A", "A"))] = 1;
dict[(new MyValue("B", "B"))] = 2;
var myContainer = new MyContainer() { MyValue = new MyValue("A Property", "At the top level"), Dictionary = dict };
var json = JsonConvert.SerializeObject(myContainer, Formatting.Indented);
Debug.WriteLine(json);
try
{
var newContainer = JsonConvert.DeserializeObject<MyContainer>(json);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString()); // No assert - no exception is thrown.
}
try
{
var dictjson = JsonConvert.SerializeObject(dict, Formatting.Indented);
Debug.WriteLine(dictjson);
var newDict = JsonConvert.DeserializeObject<Dictionary<MyValue, int>>(dictjson);
}
catch (Exception ex)
{
Debug.WriteLine("Caught expected exception deserializing dictionary directly: " + ex.ToString());
}
}
}
Sure enough, there was no exception deserializing the container, but there was deserializing the dictionary directly. And the following JSON was created for the container:
{
"MyValue": {
"Prop1": "A Property",
"Prop2": "At the top level"
},
"Dictionary": [
{
"Key": {
"Prop1": "A",
"Prop2": "A"
},
"Value": 1
},
{
"Key": {
"Prop1": "B",
"Prop2": "B"
},
"Value": 2
}
]
}
Is that what you want?
Update
Or, if you don't like the proxy arrays, you could apply the following JsonConverterAttribute
to each and every Dictionary
property to get the same result:
public class MyContainer
{
public MyContainer()
{
this.Dictionary = new Dictionary<MyValue, int>();
}
public MyValue MyValue { get; set; }
[JsonConverter(typeof(DictionaryToArrayConverter<MyValue, int>))]
public Dictionary<MyValue, int> Dictionary { get; set; }
}
public class DictionaryToArrayConverter<TKey, TValue> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Dictionary<TKey, TValue>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
KeyValuePair<TKey, TValue>[] pairs;
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
pairs = token.ToObject<KeyValuePair<TKey, TValue>[]>(serializer);
}
else
{
JArray array = new JArray();
array.Add(token);
pairs = token.ToObject<KeyValuePair<TKey, TValue>[]>(serializer);
}
if (pairs == null)
return null;
return pairs.ToDictionary(pair => pair.Key, pair => pair.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
return;
var pairs = ((IDictionary<TKey, TValue>)value).ToArray();
serializer.Serialize(writer, pairs);
}
}
Update
As an alternative, you could seal your MyValue
class and attach an appropriate TypeConverterAttribute
for converting from & to a string. JSON.Net will pick this up and use it both for dictionary keys and properties. This solution is simpler in that it's a global solution so you don't need to use proxy arrays or converter properties for each and every dictionary, however the JSON created for your MyValue
properties isn't quite what you require.
Thus:
public class MyValueConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string)
{
// Cannot do JsonConvert.DeserializeObject here because it will cause a stackoverflow exception.
using (var reader = new JsonTextReader(new StringReader((string)value)))
{
JObject item = JObject.Load(reader);
if (item == null)
return null;
MyValue myValue = new MyValue();
var prop1 = item["Prop1"];
if (prop1 != null)
myValue.Prop1 = prop1.ToString();
var prop2 = item["Prop2"];
if (prop2 != null)
myValue.Prop2 = prop2.ToString();
return myValue;
}
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
MyValue myValue = (MyValue)value;
// Cannot do JsonConvert.SerializeObject here because it will cause a stackoverflow exception.
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture))
using (JsonTextWriter jsonWriter = new JsonTextWriter(sw))
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Prop1");
jsonWriter.WriteValue(myValue.Prop1);
jsonWriter.WritePropertyName("Prop2");
jsonWriter.WriteValue(myValue.Prop2);
jsonWriter.WriteEndObject();
return sw.ToString();
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
[TypeConverter(typeof(MyValueConverter))]
public class MyValue
{
public MyValue()
{
}
public MyValue(string prop1, string prop2)
{
this.Prop1 = prop1;
this.Prop2 = prop2;
}
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
else if (ReferenceEquals(obj, null))
return false;
if (GetType() != obj.GetType())
return false;
var other = (MyValue)obj;
return Prop1 == other.Prop1 && Prop2 == other.Prop2;
}
public override int GetHashCode()
{
unchecked
{
uint code = 0;
if (Prop1 != null)
code ^= (uint)Prop1.GetHashCode();
code = (code << 16) | (code >> 16);
if (Prop2 != null)
code ^= (uint)Prop2.GetHashCode();
return (int)code;
}
}
public override string ToString()
{
return TypeDescriptor.GetConverter(GetType()).ConvertToString(this);
}
public static bool operator ==(MyValue first, MyValue second)
{
if (ReferenceEquals(first, null))
return ReferenceEquals(second, null);
return first.Equals(second);
}
public static bool operator !=(MyValue first, MyValue second)
{
return !(first == second);
}
}
Properties and dictionaries using this class can now be serialized without the use of any proxy arrays. For instance, serializing and deserializing the following:
public class MyContainer
{
public MyContainer()
{
this.Dictionary = new Dictionary<MyValue, int>();
}
public MyValue MyValue { get; set; }
public Dictionary<MyValue, int> Dictionary { get; set; }
}
Gives the following JSON when serialized:
{
"MyValue": "{\"Prop1\":\"A Property\",\"Prop2\":\"At the top level\"}",
"Dictionary": {
"{\"Prop1\":\"A\",\"Prop2\":\"A\"}": 1,
"{\"Prop1\":\"B\",\"Prop2\":\"B\"}": 2
}
}
(the quotes are escaped since they are embedded in the JSON, not part of the JSON.)
Late Update - creating a generic TypeConverter
for dictionary keys
It's possible to create a generic TypeConverter
that works for any generically specified type by using an appropriate contract resolver:
public class NoTypeConverterContractResolver : DefaultContractResolver
{
readonly Type type;
public NoTypeConverterContractResolver(Type type)
: base()
{
if (type == null)
throw new ArgumentNullException();
if (type == typeof(string) || type.IsPrimitive)
throw new ArgumentException("type == typeof(string) || type.IsPrimitive");
this.type = type;
}
protected override JsonContract CreateContract(Type objectType)
{
if (type.IsAssignableFrom(objectType))
{
// Replaces JsonStringContract for the specified type.
var contract = this.CreateObjectContract(objectType);
return contract;
}
return base.CreateContract(objectType);
}
}
public class GenericJsonTypeConverter<T> : TypeConverter
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static NoTypeConverterContractResolver contractResolver;
static NoTypeConverterContractResolver ContractResolver
{
get
{
if (contractResolver == null)
Interlocked.CompareExchange(ref contractResolver, new NoTypeConverterContractResolver(typeof(T)), null);
return contractResolver;
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string)
{
using (var reader = new JsonTextReader(new StringReader((string)value)))
{
var obj = JsonSerializer.Create(new JsonSerializerSettings { ContractResolver = ContractResolver }).Deserialize<T>(reader);
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture))
using (JsonTextWriter jsonWriter = new JsonTextWriter(sw))
{
JsonSerializer.Create(new JsonSerializerSettings { ContractResolver = ContractResolver }).Serialize(jsonWriter, value);
}
return sb.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Then apply it to your class as follows:
[TypeConverter(typeof(GenericJsonTypeConverter<MyValue>))]
public class MyValue
{
}
Why does Json.Net not properly serialize byte[] when it is dictionary key?
The author of Json.NET answered that on GitHub:
That's expected behavior. The key has to be a string. If Json.NET can't find any TypeConverter then it will call ToString.
Json.NET isn't finding a type converter, so it calls ToString
on the key. ToString
by default returns the name of the type, which in this case is "System.Byte[]".
Values of dictionaries have different behavior. The run through conversion logic, which is why they can be properly serialized as values since there is a BinaryConverter
. Why this is is only known to the author, but I suspect it is performance related as well as a little-needed feature.
You are better off not using a collection as a key in your object model. If your keys are GUIDs, then just use System.Guid
or convert the Guid to a string.
How to serialize dictionary object,object to json
You need to implement a type converter eg.MyClassConverter
for MyClass
and then add the attribute [TypeConverter(typeof(MyClassConverter))]
to the MyClass class declaration. This means that, instead of using the default ToString(), it will use the type convert that you define to serialize as you want.
The link "How to: Implement a Type Converter" shows how to create a type converter.
The SO link showing the answer is:
Not ableTo Serialize Dictionary with Complex key using Json.net
Related Topics
Passing a Value from One Form to Another Form
When Is a Custom Attribute's Constructor Run
A Simple C# Dll - How to Call It from Excel, Access, Vba, Vb6
Cannot Step into .Net Framework Source Code
How to Build a Datatemplate in C# Code
Why Should I Use Int Instead of a Byte or Short in C#
Listen to Changes of Dependency Property
How to Pass Parameters to Another Process in C#
C# Wpf Navigation Between Pages (Views)
Property(With No Extra Processing) VS Public Field
How to Make Two Transparent Layer with C#
The Need for Volatile Modifier in Double Checked Locking in .Net
Switch Case: How to Use a Range Instead of a One Number
How to Use Non-Thread-Safe Async/Await APIs and Patterns with ASP.NET Web API
Enabling C# 7 in a ASP.NET Application
How to Include External Font in Wpf Application Without Installing It