Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

How to customize XML serialization

4.67/5 (5 votes)
12 Oct 2012CPOL2 min read 69.1K   1.2K  
How to serialize an object to an XML document with a specific format.

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:

C#
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):

XML
<?xml version="1.0" encoding="utf-16" ?> 
<Product_Original xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <!--<Price>ABC</Price>-->
    <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()

C#
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()

C#
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);

    //Check the root's tag is empty or not. <Product/> mean empty.
    bool isEmpty = reader.IsEmptyElement;
    //Go into the root content.
    reader.Read();
    if (isEmpty)
        return;
    //Counter.In order to count the start tag.
    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
                {
                    //Use specific format to deserialize datetime
                    //datetimeFormat is a date format which predefine in the class
                    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
                {
                    //If system cannot deserialize the XML node.it could use default value.
                }
                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()

C#
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);
        //Use specific format to serialize datetime
        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.

Image 1

After modification, the serialization is successful.

Image 2

History

2012-10-12 First version released.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)