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

DataContractJsonSerializer deserializing List<T> throwing error

我有一个自定义异常:

[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 IConvertible" 异常:(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;
}

这是错误信息的代码 class:

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

尝试在您的 ErrorInfo class 上实施 IConvertible

我的猜测是它不能从 SerializationInfo 上下文中名为 'ErrorInfoList' 的值 'what ever' 到列表。所以我会在 ErrorInfo 上实现 IConvertible。

这里的基本问题是 ISerializable 接口最初设计(在 .Net 1 中)是为了与 BinaryFormatter. And, while BinaryFormatter serialization streams contain complete type information, JSON is weakly typed. This causes problems described in Stand-Alone JSON Serialization:

一起工作

Supported and Unsupported ISerializable Types

In general, types that implement the ISerializable interface are fully supported when serializing/deserializing JSON. However, some of these types (including some .NET Framework types) are implemented in such a way that the JSON-specific serialization aspects cause them to not deserialize correctly:

  • With ISerializable, the type of individual data members is never known in advance. This leads to a polymorphic situation similar to deserializing types into an object. As mentioned before, this may lead to loss of type information in JSON. For example, a type that serializes an enum in its ISerializable implementation and attempts to deserialize back directly into an enum (without proper casts) fails, because an enum is serialized using numbers in JSON and JSON numbers deserialize into built-in .NET numeric types (Int32, Decimal or Double). So the fact that the number used to be an enum value is lost.

您遇到的就是这种类型信息的丢失。如果您查看为您的自定义异常生成的 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 does not support type hints for collections。因此,ErrorInfoList 被反序列化为包含 ErrorInfo 对象的 object [] 数组而不是 List<ErrorInfo>,导致您看到的错误。

因此,原则上,您可以如下更改 ErrorInfoList 的初始化:

this.ErrorInfoList = ((IEnumerable<object>)info.GetValue("ErrorInfoList", typeof(object []))).Cast<ErrorInfo>().ToList();

但是,这会破坏已经正确键入条目值的二进制和 XML 数据协定反序列化。它还会破坏 Json.NET deserialization which uses a completely different mechanism, namely storing JToken values inside the SerializationInfo and deserializing on demand using a custom 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());
    }
}