Исключение некоторых свойств во время сериализации без изменения исходного класса
Я пытаюсь сериализовать объект с несколькими свойствами, но я не хочу включать все свойства в сериализацию. Кроме того, я хотел бы изменить формат даты.
Конечно, я мог бы добавить [XmlIgnore]
, но мне не разрешено изменять исходный класс.
Единственный вариант, о котором я мог думать, - создать новый класс и скопировать все содержимое между двумя классами. Но это было бы уродливо и потребовало бы большого количества ручного кода.
Возможно ли возможно создать подкласс, поскольку оригинал не является абстрактным?
Мой вопрос:
Требования:
Спасибо заранее.
Ответы
Ответ 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
}
}