在序列化过程中排除一些属性而不改变原始类

发布于 2025-01-07 15:00:22 字数 428 浏览 4 评论 0原文

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

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

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

由于原始类不是抽象的,是否可以创建一个子类?

我的问题是:

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

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

要求:

  1. 尽可能强类型化

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

提前致谢。

I'm trying to serialize an object with several properties, but I don't want to include all properties in the serialization. Also, I would like to change the date format.

Of course I could add [XmlIgnore], but I'm not allowed to change the original class.

The only option I could think of was to create a new class and copy all contents between the two classes. But that would be ugly and would require a lot of manual code.

Would it maybe be possible to maybe create a subclass, since the original is not abstract?

My question is thus:

  1. How can I exclude some properties without changing the original class?

  2. How can I customize the date format of the output XML?

Requirements:

  1. As strong typed as possible

  2. Serialized XML should be deserializable

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

甚是思念 2025-01-14 15:00:22

对于感兴趣的人,我决定使用 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);

For whoever is interested, I decided to use XmlAttributeOverrides, but made them more strong typed (I hate to type property names as strings). Here is the extension method I used for it:

    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());
    }

Then, to ignore an attribute, I can beautifully add it to the ignore list:

    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);
缱倦旧时光 2025-01-14 15:00:22

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

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

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

[编辑回应您的评论]

您可以使用一个额外的技巧来“消失”可为 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 时删除可为 null 的类型。事实上 - 现在我想了想 - 这也可能是删除其他属性的有用方法,具体取决于您的使用场景。

You may be able to exclude some properties by taking advantage of the fact that the XmlSerializer will not serialize nulls to the output. So for reference types, you can null out those properties you don't want to appear in the xml.

The resulting xml will be deserializable back into the same class, but the omitted fields will obviously be null.

However, this doesn't help for your desire to change the date format. For this, you'll need to either create a new class that has the date as a string in the format you want, or you could implement IXmlSerializable, giving you complete control over the xml. [it is worth noting that date data-type has a standard format in XML, so by changing it it won't stricly be an XML date any longer - you may not care].

[EDIT in response to your comments]

There is an additional trick you might use to "disappear" a null nullable type, but it does require a change to your class.
The serializer, when serializing MyProperty - will also check if there is a property called MyProperySpecified. If it exists and returns false, the item property is not serialized:

public class Person
{
    [XmlElement]
    public string Name { get; set; }

    [XmlElement]
    public DateTime? BirthDate { get; set; }

    public bool BirthDateSpecified
    {
        get { return BirthDate.HasValue; }
    }
}

If you are prepared to add this property you can make it remove the nullable types when null. In fact - now I think about it - this may be a useful way of removing other properties too, depending on your usage scenario.

和影子一齐双人舞 2025-01-14 15:00:22

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

更新:
我一直在研究自定义日期格式的可能性,并且据我所知,不存在漂亮的解决方案。

正如其他人提到的,一种选择是实现 IXmlSerialized。这样做的缺点是您完全负责(反)序列化整个对象(-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 对象。如果确切的类型很重要,那么这也不是一个选择。

If you're using XmlSerializer, XmlAttributeOverrides is probably what you need.

Update:
I've been investigating the possibilities of customizing the date format, and, as far as I can see, no pretty solutions exist.

One option, as has been mentioned by others, is to implement IXmlSerializable. This has the drawback that you're completely responsible for (de-)serializing the entire object(-graph).

A second option, with also quite an extensive list of drawbacks, is to subclass the base class (you mentioned it as an alternative in your post). With quite some plumbing, conversions from and to the original object, and the use of XmlAttributeOverrides you could build something like this:

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();

It ain't pretty, but it will work.

Note that you're actually (de-)serializing SubTest objects in this case. If the exact type is important, this is not going to be an option too.

み零 2025-01-14 15:00:22

第一个选项是使用 XmlAttributeOverrides 类。

或者您可以尝试创建派生类并实现 IXmlSerialized 接口< /a>

  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
    }

}

First options is to use XmlAttributeOverrides class.

Or you can try to create derived class and implement 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
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文