The main problem is that MailMessage.Headers returns a NameValueCollection that looks like a dictionary but does not implement an IDictionary<TKey, TValue> or even not a generic IDictionary . Instead, it implements non-common ICollection and IEnumerable interfaces. In fact, these interfaces consist in scrolling only keys only completely ignoring the values.
Thus, if I create a NameValueCollection like this:
public static NameValueCollection CreateCollection() { NameValueCollection collection = new NameValueCollection(); FillCollection(collection); return collection; } private static void FillCollection(NameValueCollection collection) { collection.Add("Sam", "Dot Net Perls"); collection.Add("Bill", "Microsoft"); collection.Add("Bill", "White House"); collection.Add("Sam", "IBM"); }
and serialize it with Json.NET 6.0.7, it sees that the incoming class is an nonequivalent collection and serializes it as an array :
var collection = CreateCollection(); var json = JsonConvert.SerializeObject(collection); Debug.WriteLine(json);
production:
["Sam","Bill"]
As you can see, the values have been deleted.
Then, when deserializing, Json.NET tries to convert the string array back to NameValueCollection , but has no way to do this. In particular, he tries to create a temporary list for storing read data, but gets confused by the basic type of the list and throws an exception. This might be a bug in Json.NET, but even if it didn't throw an exception, the data was already lost during storage. This can be reproduced with a simple test class, for example:
public class NameValueCollectionWrapper { public NameValueCollectionWrapper() { this.Collection = new NameValueCollection(); } public NameValueCollection Collection { get; private set; } }
So the question is do you want to read the headlines or do you want to ignore them? And if you want to read them, in what format will you receive them? If you want to send and receive them successfully, you will need to write a custom JsonConverter . This is a bit complicated, because the NameValueCollection almost like Dictionary<string, string []> , but it keeps the order of adding keys, which Dictionary not. Ideally, serialization should preserve this order. This can be done by creating and serializing the proxy IDictionary<string, string []> :
public class NameValueCollectionDictionaryWrapper<TNameValueCollection> : IDictionary<string, string[]> where TNameValueCollection : NameValueCollection, new() { readonly TNameValueCollection collection; public NameValueCollectionDictionaryWrapper() : this(new TNameValueCollection()) { } public NameValueCollectionDictionaryWrapper(TNameValueCollection collection) { this.collection = collection; } // Method instead of a property to guarantee that nobody tries to serialize it. public TNameValueCollection GetCollection() { return collection; } #region IDictionary<string,string[]> Members public void Add(string key, string[] value) { if (collection.GetValues(key) != null) throw new ArgumentException("Duplicate key " + key); foreach (var str in value) collection.Add(key, str); } public bool ContainsKey(string key) { return collection.GetValues(key) != null; } public ICollection<string> Keys { get { return collection.AllKeys; } } public bool Remove(string key) { bool found = ContainsKey(key); if (found) collection.Remove(key); return found; } public bool TryGetValue(string key, out string[] value) { value = collection.GetValues(key); return value != null; } public ICollection<string[]> Values { get { return Enumerable.Range(0, collection.Count).Select(i => collection.GetValues(i)).ToArray(); } } public string[] this[string key] { get { var value = collection.GetValues(key); if (value == null) throw new KeyNotFoundException(); return value; } set { Remove(key); Add(key, value); } } #endregion #region ICollection<KeyValuePair<string,string[]>> Members public void Add(KeyValuePair<string, string[]> item) { Add(item.Key, item.Value); } public void Clear() { collection.Clear(); } public bool Contains(KeyValuePair<string, string[]> item) { string[] value; if (!TryGetValue(item.Key, out value)) return false; return EqualityComparer<string[]>.Default.Equals(item.Value, value); // Consistent with Dictionary<TKey, TValue> } public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex) { foreach (var item in this) array[arrayIndex++] = item; } public int Count { get { return collection.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(KeyValuePair<string, string[]> item) { if (Contains(item)) return Remove(item.Key); return false; } #endregion #region IEnumerable<KeyValuePair<string,string[]>> Members public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator() { foreach (string key in collection) { yield return new KeyValuePair<string, string[]>(key, collection.GetValues(key)); } } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion }
Then create the following JsonConverter :
public class NameValueJsonConverter<TNameValueCollection> : JsonConverter where TNameValueCollection : NameValueCollection, new() { public override bool CanConvert(Type objectType) { return typeof(TNameValueCollection).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; NameValueCollectionDictionaryWrapper<TNameValueCollection> dictionaryWrapper; if (reader.TokenType != JsonToken.StartObject) {
Finally, apply NameValueJsonConverter<NameValueCollection> , like your other converters. This produces a Json-style result while maintaining order, for example:
{"Sam":["Dot Net Perls","IBM"],"Bill":["Microsoft","White House"]}
I do not have Json.NET 4.x for testing, but I doubt that it correctly serializes both the keys and the NameValueCollection values. You might want to install this version to double check what it did.
Refresh
Just checked out Json.NET 4.5.11. In this version, the NameValueCollection property in my test class NameValueCollectionWrapper serialized as an array of key strings, which are then ignored during deserialization (collection returns empty). So this is probably a regression that version Json.NET version 6 throws an exception rather than ignoring the property.