DataContractJsonSerializer反序列化列表<;T>;抛出错误
本文关键字:出错 错误 gt 反序列化 lt DataContractJsonSerializer 列表 | 更新日期: 2023-09-27 18:13:03
我有一个自定义异常:
[Serializable]
public class MyCustomException : Exception
{
public List<ErrorInfo> ErrorInfoList { get; set; }
protected MyCustomException (SerializationInfo info, StreamingContext context)
: base(info, context)
{
this.ErrorInfoList = (List<ErrorInfo>)info.GetValue("ErrorInfoList", typeof(List<ErrorInfo>));
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("ErrorInfoList ", this.ErrorInfoList, typeof(List<ErrorInfo>));
base.GetObjectData(info, context);
}
}
每当它试图反序列化时,这一行就会抛出一个"Object must implement IConvertable"异常:(List<ErrorInfo>)info.GetValue("ErrorInfoList", typeof(List<ErrorInfo>))
以下是执行序列化的代码:
using(MemoryStream memStm = new MemoryStream())
{
XmlObjectSerializer ser = new DataContractJsonSerializer(
typeof(MyCustomException),
new Type[] {
typeof(List<ErrorInfo>),
typeof(ErrorInfo)
}
);
ser.WriteObject(memStm, (MyCustomException)context.Exception);
memStm.Seek(0, SeekOrigin.Begin);
using (StreamReader streamReader = new StreamReader(memStm))
{
response.Content = new StringContent(streamReader.ReadToEnd());
}
}
以下是进行反序列化的代码:
using(MemoryStream memStm = new MemoryStream(response.Content.ReadAsByteArrayAsync().Result))
{
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(
typeof(MyCustomException),
new Type[] {
typeof(List<ErrorInfo>),
typeof(ErrorInfo)
}
);
UserPortalException upEx = (UserPortalException)deserializer.ReadObject(memStm);
throw upEx;
}
这是ErrorInfo类的代码:
[Serializable]
public class ErrorInfo : ISerializable
{
public enum ErrorCode {
[.....]
}
public ErrorCode Code { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Code", this.Code , typeof(ErrorCode ));
}
public Error(SerializationInfo info, StreamingContext context)
{
this.Code = (ErrorCode)Enum.Parse(typeof(ErrorCode), info.GetInt32("Code").ToString());
}
}
这里的基本问题是ISerializable
接口最初是(在.Net 1中(为使用BinaryFormatter
而设计的。而且,虽然BinaryFormatter
序列化流包含完整的类型信息,但JSON是弱类型的。这会导致Stand-Alone JSON Serialization:中描述的问题
支持和不支持的ISerializable类型
通常,在序列化/反序列化JSON时,完全支持实现ISerializable接口的类型。然而,其中一些类型(包括一些.NET Framework类型(的实现方式使得JSON特定的序列化方面导致它们无法正确反序列化:
- 使用ISerializable,单个数据成员的类型永远不会事先知道。这导致了类似于将类型反序列化为对象的多态情况。如前所述,这可能会导致JSON中的类型信息丢失。例如,在其ISerializable实现中序列化枚举并尝试直接反序列化回枚举(没有正确的强制转换(的类型失败,因为枚举是使用JSON中的数字序列化的,JSON数字反序列化为内置的.NET数字类型(Int32、Decimal或Double(。因此,这个数字曾经是一个枚举值的事实就消失了
你所经历的只是一种类型信息的丢失。如果您查看为自定义异常生成的JSON,您将看到:
{"ErrorInfoList":[{"__type":"ErrorInfo:#Question40048102","Code":0}],"ClassName":"Question40048102.MyCustomException","Message":null,"Data":null,"InnerException":null,"HelpURL":null,"StackTraceString":null,"RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":null,"HResult":-2146233088,"Source":null}
每个ErrorInfo
都有一个"__type"
类型提示,但ErrorInfoList
没有类型提示,因为DataContractJsonSerializer
不支持集合的类型提示。因此,ErrorInfoList
被反序列化为包含ErrorInfo
对象的object []
数组,而不是List<ErrorInfo>
,从而导致您看到的错误。
因此,原则上,您可以更改ErrorInfoList
的初始化,如下所示:
this.ErrorInfoList = ((IEnumerable<object>)info.GetValue("ErrorInfoList", typeof(object []))).Cast<ErrorInfo>().ToList();
然而,这将破坏二进制和XML数据协定的反序列化,因为在反序列化中,条目值已经正确键入。它还将破坏Json.NET反序列化,后者使用完全不同的机制,即在SerializationInfo
中存储JToken
值,并使用自定义IFormatterConverter
按需反序列化。
因此,需要一点代码气味来支持以上所有的序列化程序:
[Serializable]
[KnownType(typeof(List<ErrorInfo>))]
[KnownType(typeof(ErrorInfo))]
public class MyCustomException : Exception
{
public List<ErrorInfo> ErrorInfoList { get; set; }
public MyCustomException()
: base()
{
this.ErrorInfoList = new List<ErrorInfo>();
}
protected MyCustomException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
foreach (SerializationEntry entry in info)
{
if (entry.Name == "ErrorInfoList")
{
if (entry.Value == null)
this.ErrorInfoList = null;
else
{
if (entry.Value is List<ErrorInfo>)
{
// Already fully typed (BinaryFormatter and DataContractSerializer)
this.ErrorInfoList = (List<ErrorInfo>)entry.Value;
}
else if (entry.Value is IEnumerable && !(entry.Value is string))
{
var enumerable = (IEnumerable)entry.Value;
if (!enumerable.OfType<object>().Any())
{
// Empty collection
this.ErrorInfoList = new List<ErrorInfo>();
}
else if (enumerable.OfType<ErrorInfo>().Any())
{
// Collection is untyped but entries are typed (DataContractJsonSerializer)
this.ErrorInfoList = enumerable.OfType<ErrorInfo>().ToList();
}
}
if (this.ErrorInfoList == null)
{
// Entry value not already deserialized into a collection (typed or untyped) of ErrorInfo instances (json.net).
// Let the supplied formatter converter do the conversion.
this.ErrorInfoList = (List<ErrorInfo>)info.GetValue("ErrorInfoList", typeof(List<ErrorInfo>));
}
}
}
}
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("ErrorInfoList", this.ErrorInfoList, typeof(List<ErrorInfo>));
base.GetObjectData(info, context);
}
}
[Serializable]
[KnownType(typeof(ErrorInfo.ErrorCode))]
public class ErrorInfo : ISerializable
{
public enum ErrorCode
{
One,
Two
}
public ErrorCode Code { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Code", this.Code, typeof(ErrorCode));
}
public ErrorInfo() { }
protected ErrorInfo(SerializationInfo info, StreamingContext context)
{
this.Code = (ErrorCode)Enum.Parse(typeof(ErrorCode), info.GetInt32("Code").ToString());
}
}
尝试在ErrorInfo
类上实现IConvertible
。
我的猜测是,它不能从SerializationInfo上下文中名为"ErrorInfoList"的值中的"Whatever"变成List。所以我会在ErrorInfo上实现IConvertable。