views:

3472

answers:

3

Hello,

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.


Currenlty it looks like I have to implement IXmlSerializable for the whole Element class (it is big) and there are no simple workaround…

+2  A: 

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.

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.

ahsteele
A: 

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);
 }
}
Alex
+1  A: 

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

Regards, Raghav Salvanshi

Raghav Salvanshi
This will work, but this is the same solution as number 4) from my question. I don't want to introduce surrogate fields into public interface of my class. Thanks
Alex