Introduction
As many before me have discovered, it is not possible to directly serialize an object of type TimeSpan
using XmlSerializer
.
This tip presents two ways to do it in an easy fashion.
There is no source code to download for this tip, but all code is included in the tip.
Background
My first approach to this problem, many years ago, was to create my own TimeSpan struct
. As TimeSpan
is a sealed struct
and cannot be inherited, I had to implement all methods and properties in the original TimeSpan
and there are quite a few when you start to look into it.
Another reason for doing this was also because in .NET 1.1, TimeSpan.ToString()
contained a bug that omitted the millisecond part, if the value was less than 1 second. The effect of this bug was that values < 1 second were written as 'PT0S' when using DataSet.WriteXml
. (This was fixed in .NET 2.0.)
Lessons learned from that experience were that it was a real pain to do and maintain.
Using the Code
One assumption is that you already have a class with a TimeSpan
property that you want to serialize to an XML file or stream.
For further reading about XML Serialization, refer to this MSDN article: Introducing XML Serialization.
Solution 1
This is a very simple and straightforward solution and this is the biggest advantage.
What is done here is that one substitute property is added for the only purpose to be serialized. For all other purposes, the original property is used.
The original property is the one that will be visible for browsers and the substitute property will be serialized.
[XmlIgnore]
public TimeSpan ReadTimeout
{
get
{
return XmlConvert.ToTimeSpan(ReadTimeout_string);
}
set
{
ReadTimeout_string = XmlConvert.ToString(value);
}
}
[Browsable(false)]
[XmlElement("ReadTimeout")]
public string ReadTimeout_string { get; set; }
Only public
properties will be serialized, hence the string
variant of ReadTimeout
cannot be a private
field.
The annoying part of this is that users of this class can also access the substitute property, which may be confusing.
Solution 2
In this case, we create a new class for the purpose of serialization.
This solution requires a little bit more work, but the major benefit is that this class can be reused and it makes for a more elegant solution as no substitute property has to be added.
public class XmlTimeSpan
{
private TimeSpan m_internal = TimeSpan.Zero;
public XmlTimeSpan()
: this(TimeSpan.Zero)
{
}
public XmlTimeSpan(TimeSpan input)
{
m_internal = input;
}
public static implicit operator TimeSpan(XmlTimeSpan input)
{
return (input != null) ? input.m_internal : TimeSpan.Zero;
}
public TimeSpan ToTimeSpan()
{
return m_internal;
}
public static implicit operator XmlTimeSpan(TimeSpan input)
{
return new XmlTimeSpan(input);
}
public void FromTimeSpan(TimeSpan input)
{
this.m_internal = input;
}
[XmlText]
public string Value
{
get
{
return XmlConvert.ToString(m_internal);
}
set
{
m_internal = XmlConvert.ToTimeSpan(value);
}
}
}
The XmlText
attribute prevents an extra child node from being created. The output will be:
<ReadTimeout>P2M5.500S</ReadTimeout>
instead of:
<ReadTimeout>
<Value>P2M5.5S</Value>
</ReadTimeout>
In this case, we only need one property in the class to be serialized.
The only thing that has to be added is the attribute XmlElement
. This will redirect the serialization to use the class specified as the argument.
[XmlElement(typeof(XmlTimeSpan))]
public TimeSpan ReadTimeout { get; set; }
Points of Interest
In both solutions, I have opted for saving the value as an xsd:duration string
.
2 minutes and 4.5 seconds will be represented as PT2M4.5S. This is a personal choice and my reasoning is that it is more human readable.
Another option is to use TimeSpan.Ticks
and save the value as a long
.
[XmlText]
public long Value
{
get
{
return m_internal.Ticks;
}
set
{
m_internal = new TimeSpan(value);
}
}
One can, of course, always argue why the data has to be human readable, but when comparing configuration files between each other, I find it easier and faster to find strange values when using the XML format compared to using ticks.
History
Revision | Date | Comment |
1 | 2014-10-22 | First release |
2 | 2014-10-23 | Corrected the XML formatting and some spelling errors |