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

时间:2012-02-21 12:12:28

标签: c# xml-serialization

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

当然我可以添加[XmlIgnore],但我不允许更改原来的课程。

我能想到的唯一选择是创建一个新类并复制这两个类之间的所有内容。但这很难看,需要很多手动代码。

是否可能创建子类,因为原始文件不是抽象的?

我的问题是:

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

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

  3. 要求:

    1. 尽可能打字

    2. 序列化XML应该可以反序列化

    3. 提前致谢。

4 个答案:

答案 0 :(得分:11)

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

    public static void Add<T>(this XmlAttributeOverrides overrides, Expression<Func<T, dynamic>> 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 (e.g. 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<MyClass>(m => m.Id, ignore);
    overrides.Add<MyClass>(m => m.DateChanged, ignore);
    Type t = typeof(List<MyClass>);
    XmlSerializer serial = new XmlSerializer(t, overrides);

答案 1 :(得分:9)

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

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

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

[编辑以回应您的评论]

还有一个额外的技巧可以用来“消失”一个空的可空类型,但它确实需要更改你的类。 序列化程序在序列化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时删除可空类型。事实上 - 现在我考虑一下 - 根据您的使用场景,这可能是删除其他属性的有用方法。

答案 2 :(得分:3)

如果您使用XmlSerializerXmlAttributeOverrides可能就是您所需要的。

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

其他人提到的一个选择是实施IXmlSerializable。这样做的缺点是你完全负责(反)序列化整个对象(-graph)。

第二个选项,也有相当广泛的缺点列表,是基类的子类(你在帖子中提到它作为替代)。通过相当多的管道,来自原始对象的转换,以及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对象。如果确切类型很重要,那么这也不是一个选择。

答案 3 :(得分:2)

第一个选项是使用XmlAttributeOverrides类。

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

  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

        /// <summary>
        /// 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 <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        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
    }

}