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());
    }
}

DataContractJsonSerializer反序列化列表<;T>;抛出错误

这里的基本问题是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。

相关文章: