Search code examples
c#.netxml.net-corexml-deserialization

XmlSerializer serializing an empty list of ints in an attribute


Consider an element like this:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="XMLSchema1" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Foo">
    <xs:complexType>
      <xs:attribute name="MyList">
        <xs:simpleType>
          <xs:list itemType="xs:int" />
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
</xs:schema>

E.g.:

<Foo MyList='1 2 3' />

This corresponds to this C# class:

[Serializable]
public class Foo
{
  [XmlAttribute]
  public int[] MyList { get; set; }
}

And that’s (basically) what xsd.exe /classes generates.

Serializing and Deserializing works great, except deserializing an empty list:

var ser = new XmlSerializer(typeof(Foo));
ser.Deserialize(new StringReader("<Foo MyList='' />"));

AFAIK, this is perfectly legal XML, but the XmlSerializer will throw an InvalidOperationException:

System.InvalidOperationException: There is an error in XML document (1, 6). ---> System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFoo.Read2_Foo(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFoo.Read3_Foo()
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(TextReader textReader)
   at Blah.Program.Main(String[] args) in Program.cs:line 17

(This exception is from .NET 4.7.2; .NET Core 2.2 throws the same exception with almost identical stack trace.) The exception makes sense, as "" is not an integer.

There is this similar question, but it’s about enums, and I don’t know how to use the answer there for ints.

How can I get deserializing of an empty list working without implementing IXmlSerializable (what involves a lot of additional coding)?


Solution

  • I'm not sure there is any magic you can do with the serializer attributes to make this work, and as it's an attribute, you can't add a complex type to help hide away the problem. You could use a proxy property which handles the input as a string to work around the problem, which while it is not beautiful, avoids the need for a full blown IXmlSerializable implementation.

    [Serializable]
    public class Foo
    {
        [XmlIgnore]
        public int[] MyList { get; set; }
    
        [XmlAttribute(AttributeName = "MyList")]
        public string MyListProxy
        {
            get
            {
                if (MyList == null) return String.Empty;
                return String.Join(" ", MyList);
            }
            set
            {
                MyList = String.IsNullOrWhiteSpace(value) ? new int[0] : value
                    .Split(' ')
                    .Select(a => int.Parse(a))
                    .ToArray();
            }
        }
    }