86

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…

Community
  • 1
  • 1
Aliaksei Kliuchnikau
  • 13,589
  • 4
  • 59
  • 72

5 Answers5

72

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?); }
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • 6
    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 – Aliaksei Kliuchnikau Sep 27 '09 at 00:10
  • 12
    FWIW, I find this solution to be better than the explicit IXmlSerializable implementation (the accepted solution), although not to the OP's specific question. I avoid implementing IXmlSerializable unless absolutely required, finding that it winds up costing me more in maintenance over the long haul. In a simple case such as this and without any other mitigating factors, I will go for the "ugly" surrogate solution without giving it a second thought. – Paul Prewett Dec 24 '14 at 16:53
  • gives a bit of extra overhead, but you could of course have two classes - one for deserialization which has all these extra properties, and the other which only has the actual values. Create an implicit conversion that just returns a new instance of the class being converted to with all the correct info. – IAmJersh Oct 01 '20 at 12:05
24

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);
    }
}
Aliaksei Kliuchnikau
  • 13,589
  • 4
  • 59
  • 72
14

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.

Community
  • 1
  • 1
ahsteele
  • 26,243
  • 28
  • 134
  • 248
  • 1
    The "Using XmlSerializer to deserialize into a Nullable" link is dead. [Here's a cached version from google](http://webcache.googleusercontent.com/search?q=cache:vT5GiyOCWyIJ:www.rqna.net/qna/zzrzt-deserialise-missing-xml-attribute-to-nullable-type.html) – Anttu Jun 13 '14 at 08:09
  • @Anttu I switched the link in the answer to the Wayback Machine archive of the original *Using XmlSerializer to deserialize into a Nullable*. – ahsteele Jun 16 '14 at 21:34
3

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));
        }
    }
}
StanK
  • 4,750
  • 2
  • 22
  • 46
Levi Fuller
  • 13,631
  • 4
  • 38
  • 44
  • 1
    This is fantastic, from a code readability point of view it's a lot nicer to use this than to litter your code with deserialising to strings and then using properties to cast. I can't speak to performance or best practices but it's keeping the code neat and if I find a property that the XSD didn't declare as nullable but actually is it's a lot easier to add this in. I had to tweak the ReadXML slightly and add `if (reader.IsEmptyElement) { return; }` just before the end for self closing elements, I hope that works properly. – Matt Feb 08 '23 at 14:27
2

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

Yaakov Ellis
  • 40,752
  • 27
  • 129
  • 174
ErdemS
  • 45
  • 2
  • Copy&pasted answer from [link](https://stackoverflow.com/a/64134708/5991722) – CrudaLilium Oct 09 '20 at 08:32
  • Looks like a good idea, the problem is that create new problems, for example deserializing `bool` (can't deserialize 0 or 1 as boolean). I downvote because this warning should be present in this answer. – dani herrera Apr 25 '23 at 17:20