I get an xml from the 3rd party and I need to deserialize it into C# object. This xml may contain attributes with value of integer type or empty value: attr=”11” or attr=””. I want to deserialize this attribute value into the property with type of nullable integer. But XmlSerializer does not support deserialization into nullable types. The following test code fails during creation of XmlSerializer with InvalidOperationException {"There was an error reflecting type 'TestConsoleApplication.SerializeMe'."}.
[XmlRoot("root")]
public class SerializeMe
{
[XmlElement("element")]
public Element Element { get; set; }
}
public class Element
{
[XmlAttribute("attr")]
public int? Value { get; set; }
}
class Program {
static void Main(string[] args) {
string xml = "<root><element attr=''>valE</element></root>";
var deserializer = new XmlSerializer(typeof(SerializeMe));
Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml));
var result = (SerializeMe)deserializer.Deserialize(xmlStream);
}
}
When I change type of 'Value' property to int, deserialization fails with InvalidOperationException:
There is an error in XML document (1, 16).
Can anybody advise how to deserialize attribute with empty value into nullable type (as a null) at the same time deserializing non-empty attribute value into the integer? Is there any trick for this so I will not have to do deserialization of each field manually (actually there are a lot of them)?
Update after comment from ahsteele:
As far as I know, this attribute works only with XmlElementAttribute - this attribute specifies that the element has no content, whether child elements or body text. But I need to find the solution for XmlAttributeAttribute. Anyway I cannot change xml because I have no control over it.
This property works only when attribute value is non-empty or when attribute is missing. When attr has empty value (attr='') the XmlSerializer constructor fails (as expected).
public class Element
{
[XmlAttribute("attr")]
public int Value { get; set; }
[XmlIgnore]
public bool ValueSpecified;
}
Custom Nullable class like in this blog post by Alex Scordellis
I tried to adopt the class from this blog post to my problem:
[XmlAttribute("attr")]
public NullableInt Value { get; set; }
But XmlSerializer constructor fails with InvalidOperationException:
Cannot serialize member 'Value' of type TestConsoleApplication.NullableInt.
XmlAttribute/XmlText cannot be used to encode types implementing IXmlSerializable }
Ugly surrogate solution (shame on me that I wrote this code here :) ):
public class Element
{
[XmlAttribute("attr")]
public string SetValue { get; set; }
public int? GetValue()
{
if ( string.IsNullOrEmpty(SetValue) || SetValue.Trim().Length <= 0 )
return null;
int result;
if (int.TryParse(SetValue, out result))
return result;
return null;
}
}
But I don’t want to come up with the solution like this because it breaks interface of my class for its consumers. I would better manually implement IXmlSerializable interface.
Currently it looks like I have to implement IXmlSerializable for the whole Element class (it is big) and there are no simple workaround…
I solved this problem by implementing IXmlSerializable interface. I did not found easier way.
Here is the test code sample:
[XmlRoot("root")]
public class DeserializeMe {
[XmlArray("elements"), XmlArrayItem("element")]
public List<Element> Element { get; set; }
}
public class Element : IXmlSerializable {
public int? Value1 { get; private set; }
public float? Value2 { get; private set; }
public void ReadXml(XmlReader reader) {
string attr1 = reader.GetAttribute("attr");
string attr2 = reader.GetAttribute("attr2");
reader.Read();
Value1 = ConvertToNullable<int>(attr1);
Value2 = ConvertToNullable<float>(attr2);
}
private static T? ConvertToNullable<T>(string inputValue) where T : struct {
if ( string.IsNullOrEmpty(inputValue) || inputValue.Trim().Length == 0 ) {
return null;
}
try {
TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
return (T)conv.ConvertFrom(inputValue);
}
catch ( NotSupportedException ) {
// The conversion cannot be performed
return null;
}
}
public XmlSchema GetSchema() { return null; }
public void WriteXml(XmlWriter writer) { throw new NotImplementedException(); }
}
class TestProgram {
public static void Main(string[] args) {
string xml = @"<root><elements><element attr='11' attr2='11.3'/><element attr='' attr2=''/></elements></root>";
XmlSerializer deserializer = new XmlSerializer(typeof(DeserializeMe));
Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml));
var result = (DeserializeMe)deserializer.Deserialize(xmlStream);
}
}