Исключение некоторых свойств во время сериализации без изменения исходного класса - PullRequest
12 голосов
/ 21 февраля 2012

Я пытаюсь сериализовать объект с несколькими свойствами, но я не хочу включать все свойства в сериализацию. Кроме того, я хотел бы изменить формат даты.

Конечно, я мог бы добавить [XmlIgnore], но мне не разрешено менять исходный класс.

Единственный вариант, о котором я мог подумать, - это создать новый класс и скопировать все содержимое между двумя классами. Но это было бы ужасно и потребовало бы много ручного кода.

Возможно ли создание подкласса, поскольку оригинал не является абстрактным?

Мой вопрос таков:

  1. Как исключить некоторые свойства без изменения исходного класса?

  2. Как настроить формат даты выходного XML?

Требования:

  1. Как можно сильнее набрано

  2. Сериализованный XML должен быть десериализуемым

Заранее спасибо.

Ответы [ 4 ]

11 голосов
/ 23 февраля 2012

Для тех, кто заинтересован, я решил использовать 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);
9 голосов
/ 21 февраля 2012

Вы можете исключить некоторые свойства, воспользовавшись тем, что XmlSerializer не будет сериализовать нули в вывод.Так что для ссылочных типов вы можете обнулить те свойства, которые вы не хотите отображать в xml.

Полученный xml будет десериализуем обратно в тот же класс, но пропущенные поля, очевидно, будут нулевыми.

Однако это не поможет вашему желанию изменить формат даты.Для этого вам нужно либо создать новый класс с датой в виде строки в нужном вам формате, либо вы можете реализовать 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; }
    }
}

Если вы готовы добавить это свойство, вы можете удалить из него пустые типы, если оно равно нулю.На самом деле - теперь я думаю об этом - это может быть полезным способом удаления других свойств, в зависимости от сценария использования.

3 голосов
/ 21 февраля 2012

Если вы используете XmlSerializer, XmlAttributeOverrides, вероятно, то, что вам нужно.

Обновление: Я изучал возможности настройки формата даты, и, насколько я могу судить, симпатичных решений не существует.

Один вариант, как уже упоминалось другими, заключается в реализации IXmlSerializable. Это имеет тот недостаток, что вы полностью ответственны за (де) сериализацию всего объекта (-граф).

Второй вариант, также с довольно обширным списком недостатков, заключается в создании подкласса базового класса (вы упомянули его как альтернативу в своем посте). С некоторой долей сантехники, преобразованиями из исходного объекта и в него и использованием 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. Если точный тип важен, это тоже не вариант.

2 голосов
/ 21 февраля 2012

Первый вариант - использовать класс 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

        /// <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
    }

}
...