Introduction
In this article, I will present how to serialize an object to an XML document with
a specific format and if the data format does not match with the object expected when deserializing
the
XML document back to the object, it will use the default value and won't throw
an exception.
Background
In .NET, we usually use XmlSerializer
to convert an object to an XML document
to be able to store it in a different place. And also, we could deserialize the XML document back to an object.
However, if the data in the XML document is in a wrong format and we use
XmlSerializer
to deserialize the XML document, an exception will occur and
it fails to convert.
E.g., the class:
public class Product_Original
{
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
private double _Price;
public double Price
{
get
{
return _Price;
}
set
{
_Price = value;
}
}
private DateTime _ProductionDate;
public DateTime ProductionDate
{
get
{
return _ProductionDate;
}
set
{
_ProductionDate = value;
}
}
public Product_Original()
{
}
public Product_Original(string nameStr, double priceDouble, DateTime productionDate)
{
_Name = nameStr;
_Price = priceDouble;
_ProductionDate = productionDate;
}
}
XML snippet (after serialized by XmlSerializer
):
="1.0"="utf-16"
<Product_Original xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price>0</Price>
<ProductionDate>0001-01-01T00:00:00</ProductionDate>
</Product_Original>
In the above sample, there is a node named "Price" in the XML document expected
in double format. If we manually input "ABC" and use XmlSerializer
to deserialize the XML document. the conversion won't be a success even though other nodes are
in the right format.
In the other case, it is not allowed to serialize the object to a specific well-known format rather than the default
DateTime
format.
To do these, we need to implement IXmlSerializable
.
IXmlSerializable
The Interface IXmlSerializable
contains three methods as below:
GetSchema
: 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.ReadXml
: This method will be called during the deserialization process.WriteXml
This method will be called while serializing the object.
For more information, please visit IXmlSerializable
.
Implement GetSchema()
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
As MSDN says, when implementing the IXmlSerializable
interface, you should return
null
(Nothing
in Visual Basic) from this method.
Implement ReadXml()
public void ReadXml(System.Xml.XmlReader reader)
{
Dictionary<string, Type> propertyTypeDic = new Dictionary<string, Type>();
Dictionary<string, PropertyInfo> propertyInfoDic =
new Dictionary<string, PropertyInfo>();
XmlUtilities.GetTypePropertyDic(this.GetType(), ref propertyTypeDic,
ref propertyInfoDic);
bool isEmpty = reader.IsEmptyElement;
reader.Read();
if (isEmpty)
return;
int _tagCount = 0;
try
{
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
string nodeName = reader.Name;
if (propertyTypeDic != null && propertyTypeDic.ContainsKey(nodeName))
{
XmlSerializer valueSerializer =
new XmlSerializer(propertyTypeDic[nodeName]);
reader.ReadStartElement(nodeName);
object tempValue = null;
try
{
if (propertyTypeDic[nodeName] == typeof(System.DateTime))
{
valueSerializer = new XmlSerializer(typeof(System.String));
DateTime _tempDt = DateTime.MinValue;
string _tempDtStr =
Convert.ToString(valueSerializer.Deserialize(reader));
if (!string.IsNullOrWhiteSpace(_tempDtStr))
{
if (DateTime.TryParseExact(_tempDtStr, datetimeFormat,
CultureInfo.InvariantCulture, DateTimeStyles.None, out _tempDt))
{
if (_tempDt != DateTime.MinValue)
tempValue = _tempDt;
}
else
{
tempValue =
propertyInfoDic[nodeName].GetValue(this, null);
}
}
}
else
{
tempValue = valueSerializer.Deserialize(reader);
}
}
catch
{
}
reader.ReadEndElement();
if (tempValue != null && propertyInfoDic != null &&
propertyInfoDic.ContainsKey(nodeName))
{
propertyInfoDic[nodeName].SetValue(this, tempValue, null);
}
}
else
{
if (reader.IsStartElement())
{
reader.ReadStartElement(nodeName);
++_tagCount;
}
else
{
if (string.IsNullOrWhiteSpace(nodeName))
reader.Read();
for (int i = 0; i < _tagCount; ++i)
{
reader.ReadEndElement();
}
_tagCount = 0;
}
}
reader.MoveToContent();
}
reader.ReadEndElement();
}
catch (Exception ex)
{
throw ex;
}
}
In the above implementation, first I used reflection to get all the properties in the class
using the predefined method XmlUtilities.GetTypePropertyDic
.
And then we define a variable named _tagCount
in order to count the depth of each XML node and after reading the data
the system uses it to rollback
to the root node and loads the next next node in the root content. Finally, we loop all the nodes in the root content.
In the loop process, I add a switch case
to deserialize DateTime
which
is serialized with a specific format in the below method ---WriteXml()
.
Implement WriteXml()
public void WriteXml(System.Xml.XmlWriter writer)
{
Dictionary<string, Type> propertyTypeDic = new Dictionary<string, Type>();
Dictionary<string, PropertyInfo> propertyInfoDic =
new Dictionary<string, PropertyInfo>();
XmlUtilities.GetTypePropertyDic(this.GetType(), ref propertyTypeDic,
ref propertyInfoDic);
foreach (string key in propertyTypeDic.Keys.ToList())
{
XmlSerializer valueSerializer = new XmlSerializer(propertyTypeDic[key]);
object valueObject = propertyInfoDic[key].GetValue(this, null);
writer.WriteStartElement(key);
if (propertyTypeDic[key] == typeof(System.DateTime))
{
valueSerializer = new XmlSerializer(typeof(System.String));
DateTime? _tempDt = null;
try
{
_tempDt = Convert.ToDateTime(valueObject);
}
catch
{
_tempDt = null;
}
if (_tempDt != null)
{
valueSerializer.Serialize(writer,
_tempDt.Value.ToString(datetimeFormat));
}
}
else
{
valueSerializer.Serialize(writer, valueObject);
}
writer.WriteEndElement();
}
}
The above code shows how to serialize an object manually. First the same as ReadXml()
we get all the properties in the class using the predefined method XmlUtilities.GetTypePropertyDic
. And then
we loop through all the properties from the result and serialize with a specific format if the property type is
DateTime
.
Demo
I attach a project of WPF to show the test case and show the code.
After modification, the serialization is successful.
History
2012-10-12 First version released.