一种通用列表反序列化类?
一种通用列表反序列化类?
好的,下面是到目前为止的故事。
我已经能够使用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 = "" + " "; 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" + " " + "field1Val " + "field2Val " + "field3Val " + "field4Val " + "" + " " + "field1Val " + "field2Val " + "field3Val " + "field4Val " + "" + " " + "field1Val " + "field2Val " + "field3Val " + "field4Val " + "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 = "" + " "; XmlSerializer ser = new XmlSerializer(typeof(ItemList" + " " + "field1Val " + "field2Val " + "field3Val " + "field4Val " + "" + " " + "field1Val " + "field2Val " + "field3Val " + "field4Val " + ")); 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太过挑剔了吗?
问题的出现原因是在进行泛型列表反序列化时,无法使用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); Listfoos = GenericListDeserializer .DeserializeList(xmlDocument, "foo");
上述代码中,我们创建了一个名为GenericListDeserializer的泛型列表反序列化类。该类中有一个静态方法DeserializeList,该方法接受一个XmlDocument对象和一个元素名称作为参数,返回反序列化后的泛型列表。
在DeserializeList方法中,我们首先通过XmlDocument的GetElementsByTagName方法获取指定元素名称的XmlNodeList。然后,使用XmlSerializer将每个XmlNode反序列化为泛型类型T,并将其添加到列表中。
最后,我们可以通过调用GenericListDeserializer
一种通用列表反序列化类的问题出现的原因是,需要将XML数据反序列化为泛型列表,但没有现成的类可以直接实现该功能。为了解决这个问题,可以实现System.Xml.Serialization.IXmlSerializable接口,并使用XmlInclude属性来声明可能的派生类型。然而,这种方法的缺点是创建XmlSerializers的开销较大,可以考虑将其缓存起来以提高性能。
解决方法是创建一个名为ItemList
通过实现这个类并使用XmlInclude属性声明派生类型,即可实现将XML数据反序列化为泛型列表的功能。
.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来访问。