我写了一些自定义的JsonConverters来将json文本反序列化为System.Net.Mail.MailMessage对象.这是完整的代码,可以在LINQPad中运行.有趣的是,此代码在Json.NET 4.5.11中按预期运行:
void Main()
{
const string JsonMessage = @"{
""From"": {
""Address"": ""askywalker@theEmpire.gov"",
""DisplayName"": ""Darth Vader""
},
""Sender"": null,
""ReplyTo"": null,
""ReplyToList"": [],
""To"": [
{
""Address"": ""lskywalker@theRebellion.org"",
""DisplayName"": ""Luke Skywalker""
}
],
""Bcc"": [],
""CC"": [
{
""Address"": ""lorgana@alderaan.gov"",
""DisplayName"": ""Princess Leia""
}
],
""Priority"": 0,
""DeliveryNotificationOptions"": 0,
""Subject"": ""Family tree"",
""SubjectEncoding"": null,
""Headers"": [],
""HeadersEncoding"": null,
""Body"": ""<strong>I am your father!</strong>"",
""BodyEncoding"": ""US-ASCII"",
""BodyTransferEncoding"": -1,
""IsBodyHtml"": true,
""Attachments"": [
{
""FileName"": ""skywalker family tree.jpg"",
""ContentBase64"": ""AQIDBAU=""
}
],
""AlternateViews"": []
}";
JsonConvert.DeserializeObject<MailMessage>(JsonMessage,
new MailAddressReadConverter(), new AttachmentReadConverter(), new EncodingReadConverter()).Dump();
}
public class MailAddressReadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MailAddress);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var messageJObject = serializer.Deserialize<JObject>(reader);
if (messageJObject == null)
{
return null;
}
var address = messageJObject.GetValue("Address", StringComparison.OrdinalIgnoreCase).ToObject<string>();
JToken displayNameToken;
string displayName;
if (messageJObject.TryGetValue("DisplayName", StringComparison.OrdinalIgnoreCase, out displayNameToken)
&& !string.IsNullOrEmpty(displayName = displayNameToken.ToObject<string>()))
{
return new MailAddress(address, displayName);
}
return new MailAddress(address);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class AttachmentReadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Attachment);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var info = serializer.Deserialize<AttachmentInfo>(reader);
var attachment = info != null
? new Attachment(new MemoryStream(Convert.FromBase64String(info.ContentBase64)), "application/octet-stream")
{
ContentDisposition = { FileName = info.FileName }
}
: null;
return attachment;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
private class AttachmentInfo
{
[JsonProperty(Required = Required.Always)]
public string FileName { get; set; }
[JsonProperty(Required = Required.Always)]
public string ContentBase64 { get; set; }
}
}
public class EncodingReadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Encoding).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var encodingName = serializer.Deserialize<string>(reader);
return encodingName.NullSafe(s => Encoding.GetEncoding(s));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Run Code Online (Sandbox Code Playgroud)
例外情况是:
System.ArgumentNullException : Value cannot be null.
at System.RuntimeType.MakeGenericType(Type[] instantiation)
at Newtonsoft.Json.Serialization.JsonArrayContract.CreateWrapper(Object list)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonConverter[] converters)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, JsonConverter[] converters)
Run Code Online (Sandbox Code Playgroud)
这是JSON 6中的错误吗?难道我做错了什么?
编辑:通过进一步的调试,我已经确定问题是Headers属性.
这里的基本问题是MailMessage.Headers返回a NameValueCollection,它有点像字典,但没有实现IDictionary<TKey, TValue>,甚至不是泛型的IDictionary.相反,它实现了非通用接口ICollection和IEnumerable.这些接口实际上做的是仅遍历集合的键,完全忽略值.
因此,如果我创建一个NameValueCollection像这样:
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");
}
Run Code Online (Sandbox Code Playgroud)
并使用Json.NET 6.0.7对其进行序列化,它看到传入的类是一个非泛型集合并将其序列化为一个数组:
var collection = CreateCollection();
var json = JsonConvert.SerializeObject(collection);
Debug.WriteLine(json);
Run Code Online (Sandbox Code Playgroud)
生产:
["Sam","Bill"]
Run Code Online (Sandbox Code Playgroud)
如您所见,这些值已被剥离.
然后在反序列化时,Json.NET尝试将字符串数组转换回a NameValueCollection,但无法这样做.特别是,它尝试构造一个临时列表来保存正在读取的数据,但是对列表的基本类型感到困惑,并抛出异常.这可能是Json.NET中的一个错误,但即使它没有抛出异常,数据也已经在存储中丢失了.这可以通过简单的测试类重现,如下所示:
public class NameValueCollectionWrapper
{
public NameValueCollectionWrapper()
{
this.Collection = new NameValueCollection();
}
public NameValueCollection Collection { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)
所以,问题是,你想阅读标题,还是想忽略它们?如果你想阅读它们,你将以什么格式接收它们?如果要成功发送和接收它们,则需要编写自定义JsonConverter.这样做是有点棘手,因为NameValueCollection是几乎像一个Dictionary<string, string []>,但它保留在其中添加键的顺序其中,Dictionary没有.理想情况下,序列化应该保留该顺序.这可以通过创建和序列化来实现适配器模式包装IDictionary<string, string []>,如从一个这个答案要如何NameValueCollection中转换成JSON字符串?:
public class NameValueCollectionDictionaryAdapter<TNameValueCollection> : IDictionary<string, string[]>
where TNameValueCollection : NameValueCollection, new()
{
readonly TNameValueCollection collection;
public NameValueCollectionDictionaryAdapter() : this(new TNameValueCollection()) { }
public NameValueCollectionDictionaryAdapter(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);
if (value == null)
collection.Add(key, null);
else
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)
{
return (value = collection.GetValues(key)) != null;
}
public ICollection<string[]> Values
{
get
{
return new ReadOnlyCollectionAdapter<KeyValuePair<string, string[]>, string[]>(this, p => p.Value);
}
}
public string[] this[string key]
{
get
{
var value = collection.GetValues(key);
if (value == null)
throw new KeyNotFoundException(key);
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
}
public static class NameValueCollectionExtensions
{
public static NameValueCollectionDictionaryAdapter<TNameValueCollection> ToDictionaryAdapter<TNameValueCollection>(this TNameValueCollection collection)
where TNameValueCollection : NameValueCollection, new()
{
if (collection == null)
throw new ArgumentNullException();
return new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
}
}
public class ReadOnlyCollectionAdapter<TIn, TOut> : CollectionAdapterBase<TIn, TOut, ICollection<TIn>>
{
public ReadOnlyCollectionAdapter(ICollection<TIn> collection, Func<TIn, TOut> toOuter)
: base(() => collection, toOuter)
{
}
public override void Add(TOut item) { throw new NotImplementedException(); }
public override void Clear() { throw new NotImplementedException(); }
public override bool IsReadOnly { get { return true; } }
public override bool Remove(TOut item) { throw new NotImplementedException(); }
}
public abstract class CollectionAdapterBase<TIn, TOut, TCollection> : ICollection<TOut>
where TCollection : ICollection<TIn>
{
readonly Func<TCollection> getCollection;
readonly Func<TIn, TOut> toOuter;
public CollectionAdapterBase(Func<TCollection> getCollection, Func<TIn, TOut> toOuter)
{
if (getCollection == null || toOuter == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
this.toOuter = toOuter;
}
protected TCollection Collection { get { return getCollection(); } }
protected TOut ToOuter(TIn inner) { return toOuter(inner); }
#region ICollection<TOut> Members
public abstract void Add(TOut item);
public abstract void Clear();
public virtual bool Contains(TOut item)
{
var comparer = EqualityComparer<TOut>.Default;
foreach (var member in Collection)
if (comparer.Equals(item, ToOuter(member)))
return true;
return false;
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count { get { return Collection.Count; } }
public abstract bool IsReadOnly { get; }
public abstract bool Remove(TOut item);
#endregion
#region IEnumerable<TOut> Members
public IEnumerator<TOut> GetEnumerator()
{
foreach (var item in Collection)
yield return ToOuter(item);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
}
Run Code Online (Sandbox Code Playgroud)
接下来,创建以下JsonConverter序列,其中序列化和反序列化a NameValueCollection并以旧的格式跳过值:
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.SkipComments().TokenType == JsonToken.Null)
return null;
var collection = (TNameValueCollection)existingValue ?? new TNameValueCollection();
var dictionaryWrapper = collection.ToDictionaryAdapter();
if (reader.TokenType != JsonToken.StartObject)
{
// Old buggy name value collection format in which the values were not written and so cannot be recovered.
// Skip the token and all its children.
reader.Skip();
}
else
{
serializer.Populate(reader, dictionaryWrapper);
}
return collection;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var collection = (TNameValueCollection)value;
var dictionaryWrapper = new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
serializer.Serialize(writer, dictionaryWrapper);
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
Run Code Online (Sandbox Code Playgroud)
最后,NameValueJsonConverter<NameValueCollection>像你做其他转换器一样申请.这会在保留顺序的同时以Json字典样式生成输出,例如:
{"Sam":["Dot Net Perls","IBM"],"Bill":["Microsoft","White House"]}
Run Code Online (Sandbox Code Playgroud)
我没有Json.NET 4.x可供测试,但我怀疑它是否正确序列化了一个键和值NameValueCollection.您可能希望安装该版本以重复检查它的功能.
更新
刚刚查看了Json.NET 4.5.11.在该版本中NameValueCollection,我的NameValueCollectionWrapper测试类中的属性被序列化为一个键字符串数组,然后在反序列化时被忽略(该集合返回空).因此,Json.NET版本6抛出异常而不是忽略该属性可能是一种回归.
| 归档时间: |
|
| 查看次数: |
1436 次 |
| 最近记录: |