Deserializing empty xml attribute value into nullable int property using XmlSerializer

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:

  1. Xsi:nil attribute

    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.

  2. bool *Specified property

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

  4. 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…

67548 次浏览

I've been messing around with serialization a lot myself of late and have found the following articles and posts helpful when dealing with null data for value types.

The answer to How to make a value type nullable with XmlSerializer in C# - serialization details a pretty nifty trick of the XmlSerializer. Specifically, the XmlSerialier looks for a XXXSpecified boolean property to determine if it should be included which allows you to ignore nulls.

Alex Scordellis asked a StackOverflow question which received a good answer. Alex also did a good writeup on his blog about the problem he was trying to solve Using XmlSerializer to deserialize into a Nullable<int>.

The MSDN documentation on Xsi:nil Attribute Binding Support is also useful. As is the documentation on IXmlSerializable Interface, though writing your own implementation should be your last resort.

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

This should work:

[XmlIgnore]
public int? Age { get; set; }


[XmlElement("Age")]
public string AgeAsText
{
get { return (Age.HasValue) ? Age.ToString() : null; }
set { Age = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?); }
}

Thought I might as well throw my answer into the hat: Solved this issue by creating a custom type that implements the IXmlSerializable interface:

Say you have a an XML object with the following nodes:

<ItemOne>10</Item2>
<ItemTwo />

The object to represent them:

public class MyItems {
[XmlElement("ItemOne")]
public int ItemOne { get; set; }


[XmlElement("ItemTwo")]
public CustomNullable<int> ItemTwo { get; set; } // will throw exception if empty element and type is int
}

Dynamic nullable struct to represent any potential nullable entries along with conversion

public struct CustomNullable<T> : IXmlSerializable where T: struct {
private T value;
private bool hasValue;


public bool HasValue {
get { return hasValue; }
}


public T Value {
get { return value; }
}


private CustomNullable(T value) {
this.hasValue = true;
this.value = value;
}


public XmlSchema GetSchema() {
return null;
}


public void ReadXml(XmlReader reader) {
string strValue = reader.ReadString();
if (String.IsNullOrEmpty(strValue)) {
this.hasValue = false;
}
else {
T convertedValue = strValue.To<T>();
this.value = convertedValue;
this.hasValue = true;
}
reader.ReadEndElement();


}


public void WriteXml(XmlWriter writer) {
throw new NotImplementedException();
}


public static implicit operator CustomNullable<T>(T value) {
return new CustomNullable<T>(value);
}


}


public static class ObjectExtensions {
public static T To<T>(this object value) {
Type t = typeof(T);
// Get the type that was made nullable.
Type valueType = Nullable.GetUnderlyingType(typeof(T));
if (valueType != null) {
// Nullable type.
if (value == null) {
// you may want to do something different here.
return default(T);
}
else {
// Convert to the value type.
object result = Convert.ChangeType(value, valueType);
// Cast the value type to the nullable type.
return (T)result;
}
}
else {
// Not nullable.
return (T)Convert.ChangeType(value, typeof(T));
}
}
}

You can also do this by loading the xml into an XmlDocument and then deserializing this into Json to get the object T that you are looking for.

        public static T XmlToModel<T>(string xml)
{


XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);


string jsonText = JsonConvert.SerializeXmlNode(doc);


T result = JsonConvert.DeserializeObject<T>(jsonText);


return result;
}