一种通用列表反序列化类?

25 浏览
0 Comments

一种通用列表反序列化类?

好的,下面是到目前为止的故事。

我已经能够使用XmlSerializer解析个别对象,但是解析列表一直让我头疼。我开始尝试序列化List,但是序列化器在根元素内序列化了多个 XML结构。这证明解析这个结构是有问题的,所以看起来我需要自己定义'ArrayOfFoo'元素。所以,我有一个可以包装列表的类,如下所示:

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace XmlTester2
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("XML tester...");
            string xml =
                "" +
                "" + "field1Val" +
                "field2Val" + "field3Val" +
                "field4Val" + "" +
                "" + "field1Val" +
                "field2Val" + "field3Val" +
                "field4Val" + "" +
                "" + "field1Val" +
                "field2Val" + "field3Val" +
                "field4Val" + "" + "";
            XmlSerializer ser = new XmlSerializer(typeof(ItemList));
            using (var reader = new StringReader(xml))
            {
                ItemList result = (ItemList)ser.Deserialize(reader);
            }
            Console.WriteLine("Break here and check 'result' in Quickwatch...");
            Console.ReadKey();
        }
    }
    [XmlRootAttribute(IsNullable = false)]
    public class ItemList
    {
        [XmlElementAttribute("Person")]
        public List Persons { get; set; }
        [XmlElementAttribute("Account")]
        public List Accounts { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonI2))]
    public class Person
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
    public class PersonI2 : Person
    {
        public string Field4 { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountI2))]
    public class Account
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
    public class AccountI2 : Account
    {
        public string Field4 { get; set; }
    }
}

然而,这个'wrapper',ItemList,仍然必须手动定义可能包含的所有元素(在示例中为Person和Account)。真正理想的情况是有一个通用的列表包装器类。我知道这有点希望太大,但有没有办法做到这一点呢?我在思考类似于以下的东西(这不起作用,只是给你一个大致的想法):

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace XmlTester3
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("XML tester...");
            string xml =
                "" +
                "" + 
                "field1Val" +
                "field2Val" + 
                "field3Val" +
                "field4Val" + 
                "" +
                "" + 
                "field1Val" +
                "field2Val" + 
                "field3Val" +
                "field4Val" + 
                "" + 
                "";
            XmlSerializer ser = new XmlSerializer(typeof(ItemList));
            using (var reader = new StringReader(xml))
            {
                ItemList result = (ItemList)ser.Deserialize(reader);
            }
            Console.WriteLine("Break here and check 'result' in Quickwatch...");
            Console.ReadKey();
        }
    }
    [XmlRootAttribute(IsNullable = false)]
    [XmlInclude(typeof(Person))]
    [XmlInclude(typeof(PersonI2))]
    [XmlInclude(typeof(Account))]
    [XmlInclude(typeof(AccountI2))]
    public class ItemList
    {
        [XmlElementAttribute]
        public List Items { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonI2))]
    public class Person
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
    public class PersonI2 : Person
    {
        public string Field4 { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountI2))]
    public class Account
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }
    [XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
    public class AccountI2 : Account
    {
        public string Field4 { get; set; }
    }
}

因此,在ItemList中传递的XML结构只能是一个类型,比如在这个例子中是Person,我可以定义一个ItemList,它可以让我解析一个包含多个Person对象的列表?有什么想法吗?如果必要,我不介意为每个可能包含集合的类型标记ItemList类使用[XmlInclude...]

我猜这是可能的,只是我还没有弄清楚如何? 🙂 或者默认的XmlSerializer太过挑剔了吗?

0
0 Comments

问题的出现原因是在进行泛型列表反序列化时,无法使用XmlArrayItemAttribute来动态指定类型和元素名称。这是因为XmlArrayItemAttribute只能接受具体的类型作为参数,无法接受泛型类型。

解决方法是创建一个自定义的泛型列表反序列化类,该类具有动态指定类型和元素名称的功能。可以通过以下方式实现:

public class GenericListDeserializer
{
    public static List DeserializeList(XmlDocument xmlDocument, string elementName)
    {
        List list = new List();
        XmlNodeList nodeList = xmlDocument.GetElementsByTagName(elementName);
        foreach (XmlNode node in nodeList)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            T item = (T)serializer.Deserialize(new XmlNodeReader(node));
            list.Add(item);
        }
        return list;
    }
}

使用示例:

XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlString);
List foos = GenericListDeserializer.DeserializeList(xmlDocument, "foo");

上述代码中,我们创建了一个名为GenericListDeserializer的泛型列表反序列化类。该类中有一个静态方法DeserializeList,该方法接受一个XmlDocument对象和一个元素名称作为参数,返回反序列化后的泛型列表。

在DeserializeList方法中,我们首先通过XmlDocument的GetElementsByTagName方法获取指定元素名称的XmlNodeList。然后,使用XmlSerializer将每个XmlNode反序列化为泛型类型T,并将其添加到列表中。

最后,我们可以通过调用GenericListDeserializer.DeserializeList方法来反序列化名为"foo"的元素,并将结果存储在List类型的变量foos中。

0
0 Comments

一种通用列表反序列化类的问题出现的原因是,需要将XML数据反序列化为泛型列表,但没有现成的类可以直接实现该功能。为了解决这个问题,可以实现System.Xml.Serialization.IXmlSerializable接口,并使用XmlInclude属性来声明可能的派生类型。然而,这种方法的缺点是创建XmlSerializers的开销较大,可以考虑将其缓存起来以提高性能。

解决方法是创建一个名为ItemList的泛型类,实现IXmlSerializable接口。这个类中包含一个名为Items的属性,类型为List,用于存储反序列化后的数据。在类中还定义了一个LoadSchema()方法,用于加载派生类型的架构,并返回一个Map对象,其中存储了派生类型的名称和对应的XmlSerializer对象。在ReadXml()方法中,根据XML数据的深度遍历节点并反序列化为相应的派生类型,并将其添加到Items列表中。在WriteXml()方法中,根据Items列表中的对象类型获取对应的XmlSerializer对象,并将对象序列化为XML数据。

通过实现这个类并使用XmlInclude属性声明派生类型,即可实现将XML数据反序列化为泛型列表的功能。

0
0 Comments

.NET 3.5 SP1中引入了WCF的Serializers,可以在不显式标记类的DataContract或Serializable属性的情况下对对象进行反序列化。只要属性名称与元素名称匹配,几乎任何类都可以通过这种方式进行反序列化。如果出现反序列化错误,则可能是因为某些属性命名错误或类型不正确。为了检查序列化器所期望的输入,可以先填充一个对象,然后将其序列化为XML进行比较。此前我写了一个帮助类来使用它。

这个帮助类的使用方式如下:

string serialized = "some xml";
MyType foo = Helpers.Deserialize(serialized, SerializerType.Xml); 

下面是实际的帮助类代码:

using System.IO;
using System.Runtime.Serialization; // System.Runtime.Serialization.dll (.NET 3.0)
using System.Runtime.Serialization.Json; // System.ServiceModel.Web.dll (.NET 3.5)
using System.Text;
namespace Serialization
{
    public static class Helpers
    {
        // 声明要使用的序列化器类型
        public enum SerializerType
        {
            Xml, // 使用DataContractSerializer
            Json // 使用DataContractJsonSerializer
        }
        public static T Deserialize(string SerializedString, SerializerType UseSerializer)
        {
            // 将字符串转换为流
            using (Stream s = new MemoryStream(UTF8Encoding.UTF8.GetBytes(SerializedString)))
            {
                T item;
                switch (UseSerializer)
                {
                    case SerializerType.Json:
                        // 声明要处理的类型的序列化器
                        var serJson = new DataContractJsonSerializer(typeof(T));
                        // 使用序列化器进行读取(反序列化)并转换类型
                        item = (T)serJson.ReadObject(s);
                        break;
                    case SerializerType.Xml:
                    default:
                        var serXml = new DataContractSerializer(typeof(T));
                        item = (T)serXml.ReadObject(s);
                        break;
                }
                return item;
            }
        }
        public static string Serialize(T ObjectToSerialize, SerializerType UseSerializer)
        {
            using (MemoryStream serialiserStream = new MemoryStream())
            {
                string serialisedString = null;
                switch (UseSerializer)
                {
                    case SerializerType.Json:
                        // 初始化序列化器并指定要序列化的类型
                        DataContractJsonSerializer serJson = new DataContractJsonSerializer(typeof(T));
                        // 序列化器将对象的序列化表示填充到流中
                        serJson.WriteObject(serialiserStream, ObjectToSerialize);
                        break;
                    case SerializerType.Xml:
                    default:
                        DataContractSerializer serXml = new DataContractSerializer(typeof(T));
                        serXml.WriteObject(serialiserStream, ObjectToSerialize);
                        break;
                }
                // 将流回到起始位置以便读取
                serialiserStream.Position = 0;
                using (StreamReader sr = new StreamReader(serialiserStream))
                {
                    // 使用StreamReader获取序列化文本
                    serialisedString = sr.ReadToEnd();
                    sr.Close();
                }
                return serialisedString;
            }
        }
    }
}

但问题是,如果我想要一个通用的ItemList包装类,允许我对通用项目列表进行序列化和反序列化,我无法预先知道在反序列化时ItemList中需要包含的属性的名称,它们将根据被序列化的类型而不同。我需要一种通用的方法来变化反序列化器所期望的内容。例如,如果我创建一个ItemList,它期望的是[...]

啊,我误解了。我以为你已经提前知道属性并有一个类来进行反序列化。如果你在编译时不知道属性,那么在.NET 4.0之前,你需要通过字典访问属性,或者只是使用xpath来访问。

0