【问题标题】:JSON deseralization to abstract list using DataContractJsonSerializer使用 DataContractJsonSerializer 将 JSON 反序列化为抽象列表
【发布时间】:2011-04-27 15:24:50
【问题描述】:

我正在尝试将 JSon 文件反序列化为包含抽象列表的类的实例。将实例序列化为 Json 效果很好(检查下面的 json 文件)。反序列化时,我收到带有消息“无法创建抽象类”的“System.MemberAccessException”。显然解串器正在尝试实例化抽象类而不是具体类。

在我的示例中,反序列化的类称为 ElementContainer:

namespace Data
{
    [DataContract]
    [KnownType(typeof(ElementA))]
    [KnownType(typeof(ElementB))]
    public class ElementContainer
    {
        [DataMember]
        public List<Element> Elements { get; set; }
    }

    [DataContract]
    public abstract class Element
    {
    }

    [DataContract]
    public class ElementA : Element
    {
        [DataMember]
        int Id { get; set; }
    }

    [DataContract]
    public class ElementB : Element
    {
        [DataMember]
        string Name { get; set; }
    }
}

这是已序列化的 Json 文件,我正在尝试反序列化。注意反序列化器创建具体类的“__type”字段:

{
    "Elements":
    [
        {
            "__type":"ElementA:#Data",
            "Id":1
        }, 
        {
            "__type":"ElementB:#Data",
            "Name":"MyName"
        }       
    ]
}

以下是我用于反序列化的代码:

    public T LoadFromJSON<T>(string filePath)
    {
        try
        {
            using (FileStream stream = File.OpenRead(filePath))
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
                T contract = (T)serializer.ReadObject(stream);
                return contract;
            }
        }
        catch (System.Exception ex)
        {
            logger.Error("Cannot deserialize json " + filePath, ex);
            throw;
        }
    }

是否可以使反序列化工作?

谢谢!

【问题讨论】:

  • 您是否尝试过将列表的类型更改为对象并查看发生了什么?
  • 我试过了,但没有任何改变。

标签: c# json list serialization abstract


【解决方案1】:

我们已经找到了它不起作用的原因。就在对象序列化之后,我们标识结果字符串以提高可读性。然后我们将字符串写入文件:

    public void SaveContractToJSON<T>(T contract, string filePath)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
            serializer.WriteObject(stream, contract);
            string json = Encoding.UTF8.GetString(stream.ToArray());
            File.WriteAllText(filePath, json.IndentJSON());
        }
    }

标识实际上是反序列化不起作用的原因。看来 DataContractJsonSerializer 的解析器真的很挑剔。如果某些字符位于字符 { 和字段“__type”之间,则序列化程序会丢失。

例如这个字符串会正确序列化:

"{\"Elements\":[{\"__type\":\"ElementA:#Data\",\"Id\":1}]}"

但是下一个字符串不会序列化。

"{\"Elements\":[   {\"__type\":\"ElementA:#Data\",\"Id\":1}]}"

唯一的区别是“__type”之前的空格字符。序列化将抛出 MemberAccessException。这是一种误导,因为这种行为仅在反序列化为抽象列表时才会出现。无论字符如何,序列化为抽象字段都可以正常工作。

要在不删除文件可读性的情况下解决此问题,可以在反序列化之前修改字符串。例如:

    public T LoadContractFromJSON<T>(string filePath)
    {
        try
        {
            string text = File.ReadAllText(filePath);
            text  = Regex.Replace(text, "\\{[\\n\\r ]*\"__type", "{\"__type");
            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(text)))
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
                T contract = (T)serializer.ReadObject(stream);
                return contract;
            }
        }
        catch (System.Exception ex)
        {
            logger.Error("Cannot deserialize json " + filePath, ex);
            throw;
        }
    }

【讨论】:

  • 发布的\\{[\\n\\r ]*\"__type 模式很危险,它会区分在__type 之前序列化的任何属性或拾取包含文本"__type 的任何属性以及使用隐式空格而不是显式@987654328 @ 用于空格和特定于平台的换行符序列。
  • __type 类似于 DataContractJsonSerializer 的关键字。它必须放在任何其他字段之前(实际上是在任何其他字符之前),否则 json 不会以正确的类型序列化。关于平台特定的字符,我会改变它。谢谢。
  • 简单地将 __type 作为对象的第一个属性对我有用。反序列化前无需替换为正则表达式。
  • 我遇到了类似的问题,但仅限于生产。在本地,使用相同的数据库和代码工作正常。可能是某些设置导致它在一个环境中而不是在另一个环境中插入空格?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多