【问题标题】:DataContractJsonSerializer deserializing List<T> throwing errorDataContractJsonSerializer 反序列化 List<T> 抛出错误
【发布时间】:2016-10-14 16:40:23
【问题描述】:

我有一个自定义异常:

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

每当它尝试反序列化时,此行都会引发“对象必须实现 IConvertible”异常:(List&lt;ErrorInfo&gt;)info.GetValue("ErrorInfoList", typeof(List&lt;ErrorInfo&gt;))

这是执行序列化的代码:

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

【问题讨论】:

  • 抛出的错误是不言自明的还是这里还有其他问题?
  • 我不明白如何修复异常...哪个类需要实现IConvertible?
  • ErrorInfo 类的代码在哪里?
  • 我刚刚将它添加到问题中。

标签: c# serialization deserialization datacontractserializer datacontractjsonserializer


【解决方案1】:

这里的基本问题是ISerializable 接口最初设计(在.Net 1 中)与BinaryFormatter 一起使用。而且,虽然BinaryFormatter 序列化流包含完整的类型信息,但 JSON 是弱类型的。这会导致Stand-Alone JSON Serialization 中描述的问题:

支持和不支持的 ISerializable 类型

一般来说,在序列化/反序列化 JSON 时,完全支持实现 ISerializable 接口的类型。但是,其中一些类型(包括一些 .NET Framework 类型)的实现方式是,特定于 JSON 的序列化方面会导致它们无法正确反序列化:

  • 使用 ISerializable,单个数据成员的类型永远不会提前知道。这导致了类似于将类型反序列化为对象的多态情况。如前所述,这可能会导致 JSON 中的类型信息丢失。例如,在其 ISerializable 实现中序列化枚举并尝试直接反序列化回枚举(没有正确强制转换)的类型会失败,因为枚举使用 JSON 中的数字进行序列化,而 JSON 数字反序列化为内置的 .NET 数字类型(Int32、十进制或双精度)。所以这个数字曾经是一个枚举值的事实就丢失了。

您正在经历的只是类型信息的丢失。如果查看为自定义异常生成的 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&lt;ErrorInfo&gt;,从而导致您看到的错误。

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

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

但是,这会破坏已正确键入条目值的二进制和 XML 数据协定反序列化。它还会破坏Json.NET deserialization,它使用完全不同的机制,即将JToken 值存储在SerializationInfo 中,并使用自定义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());
    }
}

【讨论】:

    【解决方案2】:

    尝试在您的 ErrorInfo 类上实现 IConvertible

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

    【讨论】:

    • 我在ErrorInfo 类上实现了IConvertible,但我遇到了同样的异常。不仅如此,我什至没有在ErrorInfoIConvertible 方法中遇到任何断点...
    猜你喜欢
    • 1970-01-01
    • 2011-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多