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