Подтвердить что ты не робот

Исключение некоторых свойств во время сериализации без изменения исходного класса

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

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

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

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

Мой вопрос:

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

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

Требования:

  • Как можно сильнее набирать

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

Спасибо заранее.

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

}