在序列化期间排除某些属性而不更改原始类

我正在尝试序列化具有多个属性的对象,但我不想在序列化中包含所有属性。 另外,我想更改日期格式。

当然我可以添加[XmlIgnore] ,但我不允许更改原始类。

我能想到的唯一选择是创建一个新类并复制这两个类之间的所有内容。 但那将是丑陋的,需要大量的手动代码。

是否有可能创建一个子类,因为原始不是抽象的?

我的问题是:

  1. 如何在不更改原始类的情况下排除某些属性?

  2. 如何自定义输出XML的日期格式?

要求:

  1. 尽可能强大的打字

  2. 序列化的XML应该是可反序列化的

提前致谢。

对于有兴趣的人,我决定使用XmlAttributeOverrides ,但是使它们更强类型(我讨厌将属性名称键入字符串)。 这是我用过的扩展方法:

  public static void Add(this XmlAttributeOverrides overrides, Expression> propertySelector, XmlAttributes attributes) { overrides.Add(typeof(T), propertySelector.BuildString(), attributes); } public static string BuildString(this Expression propertySelector) { switch (propertySelector.NodeType) { case ExpressionType.Lambda: LambdaExpression lambdaExpression = (LambdaExpression)propertySelector; return BuildString(lambdaExpression.Body); case ExpressionType.Convert: case ExpressionType.Quote: UnaryExpression unaryExpression = (UnaryExpression)propertySelector; return BuildString(unaryExpression.Operand); case ExpressionType.MemberAccess: MemberExpression memberExpression = (MemberExpression)propertySelector; MemberInfo propertyInfo = memberExpression.Member; if (memberExpression.Expression is ParameterExpression) { return propertyInfo.Name; } else { // we've got a nested property (eg MyType.SomeProperty.SomeNestedProperty) return BuildString(memberExpression.Expression) + "." + propertyInfo.Name; } default: // drop out and throw break; } throw new InvalidOperationException("Expression must be a member expression: " + propertySelector.ToString()); } 

然后,要忽略属性,我可以将它精美地添加到忽略列表中:

  var overrides = new XmlAttributeOverrides(); var ignore = new XmlAttributes { XmlIgnore = true }; overrides.Add(m => m.Id, ignore); overrides.Add(m => m.DateChanged, ignore); Type t = typeof(List); XmlSerializer serial = new XmlSerializer(t, overrides); 

您可以通过利用XmlSerializer不会将空值序列化到输出的事实来排除某些属性。 因此,对于引用类型,您可以忽略那些不希望出现在xml中的属性。

生成的xml可以反序列化回同一个类,但省略的字段显然是null。

但是,这对您更改日期格式的愿望没有帮助。 为此,您需要创建一个以您想要的格式将日期作为字符串的新类,或者您可以实现IXmlSerializable ,从而完全控制xml。 [值得注意的是,日期数据类型在XML中具有标准格式,因此通过更改它将不再是XML日期 – 您可能不关心]。

[ 编辑回应您的意见]

还有一个额外的技巧可以用来“消失”null null null类型,但它确实需要更改你的类。 序列化程序在序列化MyProperty时还会检查是否存在名为MyProperySpecified的属性。 如果它存在并返回false,则不会序列化item属性:

 public class Person { [XmlElement] public string Name { get; set; } [XmlElement] public DateTime? BirthDate { get; set; } public bool BirthDateSpecified { get { return BirthDate.HasValue; } } } 

如果您准备添加此属性,则可以在null时删除可空类型。 实际上 – 现在我考虑一下 – 根据您的使用场景,这可能也是删除其他属性的有用方法。

如果您正在使用XmlSerializer ,那么XmlAttributeOverrides可能就是您所需要的。

更新:我一直在调查自定义日期格式的可能性,据我所知,没有漂亮的解决方案。

正如其他人所提到的,一种选择是实现IXmlSerializable 。 这样做的缺点是你完全负责(取消)序列化整个对象(-graph)。

第二个选项,也有相当广泛的缺点列表,是基类的子类(你在post中提到它作为替代)。 通过相当多的管道,从原始对象转换到原始对象,以及使用XmlAttributeOverrides您可以构建如下内容:

 public class Test { public int Prop { get; set; } public DateTime TheDate { get; set; } } public class SubTest : Test { private string _customizedDate; public string CustomizedDate { get { return TheDate.ToString("yyyyMMdd"); } set { _customizedDate = value; TheDate = DateTime.ParseExact(_customizedDate, "yyyyMMdd", null); } } public Test Convert() { return new Test() { Prop = this.Prop }; } } // Serialize XmlAttributeOverrides overrides = new XmlAttributeOverrides(); XmlAttributes attributes = new XmlAttributes(); attributes.XmlIgnore = true; overrides.Add(typeof(Test), "TheDate", attributes); XmlSerializer xs = new XmlSerializer(typeof(SubTest), overrides); SubTest t = new SubTest() { Prop = 10, TheDate = DateTime.Now, CustomizedDate="20120221" }; xs.Serialize(fs, t); // Deserialize XmlSerializer xs = new XmlSerializer(typeof(SubTest)); SubTest t = (SubTest)xs.Deserialize(fs); Test test = t.Convert(); 

它不漂亮,但它会起作用。

请注意,在这种情况下,您实际上(取消)序列化SubTest对象。 如果确切的类型很重要,那么这也不是一个选择。

第一个选项是使用XmlAttributeOverrides类。

或者您可以尝试创建派生类并实现IXmlSerializable接口

  public class Program { static void Main(string[] args) { StringWriter sr1 = new StringWriter(); var baseSerializer = new XmlSerializer(typeof(Human)); var human = new Human {Age = 30, Continent = Continent.America}; baseSerializer.Serialize(sr1, human); Console.WriteLine(sr1.ToString()); Console.WriteLine(); StringWriter sr2 = new StringWriter(); var specialSerializer = new XmlSerializer(typeof(SpecialHuman)); var special = new SpecialHuman() {Age = 40, Continent = Continent.Africa}; specialSerializer.Serialize(sr2, special); Console.WriteLine(sr2.ToString()); Console.ReadLine(); } public enum Continent { Europe, America, Africa } public class Human { public int Age { get; set; } public Continent Continent { get; set; } } [XmlRoot("Human")] public class SpecialHuman : Human, IXmlSerializable { #region Implementation of IXmlSerializable ///  /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the  to the class. ///  ///  /// An  that describes the XML representation of the object that is produced by the  method and consumed by the  method. ///  public XmlSchema GetSchema() { throw new NotImplementedException(); } public void ReadXml(XmlReader reader) { throw new NotImplementedException(); } public void WriteXml(XmlWriter writer) { writer.WriteElementString("Age", Age.ToString()); switch(Continent) { case Continent.Europe: case Continent.America: writer.WriteElementString("Continent", this.Continent.ToString()); break; case Continent.Africa: break; default: throw new ArgumentOutOfRangeException(); } } #endregion } }