Search code examples
.netxml-serializationxmlserializer

How can I make XmlSerializer work with xsi:type?


I have the following fairly simply XML that I'm trying to deserialize:

<props xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://foo.com/bar">
  <prop name="foo1" xsi:type="xsd:string">bar1</prop>
  <prop name="foo2" xsi:type="xsd:int">2</prop>
</props>

When I run XSD.exe over this XML to produce a schema and then run it again to produce C# classes, I end up with highly-decorated versions of the following:

public partial class props
{
    [XmlElement("prop", IsNullable = true)]
    propsProp[] Items { get; set; }
}

public partial class propsProp
{
    [XmlAttribute]
    public string name { get; set; }
    [XmlText]
    public string Value { get; set; }
}

Now when I try to deserialize the XML into these classes using XmlSerializer, I get the following exception:

System.InvalidOperationException: There is an error in XML document (4, 4). --->
System.InvalidOperationException: The specified type was not recognized: name='string', namespace='http://www.w3.org/2001/XMLSchema', at <prop xmlns='http://foo.com/bar'>.

The xsi:type attribute is presumably there to facilitate some polymorphism on the prop value, but I don't care about that - I just want the value in a C# property.

What am I doing wrong here? How can I get that XML into a C# class?


Solution

  • I went through exactly the same issue and it's solution is pretty straightforward for reference predefined type but a little bit tricky for primitive types.

    First what is the benefit of using Using xsi:type. I found this link is so helpful and it helps me solving the issue.

    So we use xsi:type in XML to refer to driven types, for example below:

     <X xsi:type="X1">
    

    Means that X and X1 are driven types from each others, So i fixed the deserialization by using inheritance, via creating a new class for the child type and make it inherit from the parent, also we need to create a list of it:

        [XmlRoot(ElementName = "X1")]
        public class X1: X
        {
        }
    
        [XmlRoot(ElementName = "X1List")]
        public class X1List
        {
            [XmlElement(ElementName = "X1")]
            public X1 X1{ get; set; }
        }
    

    And in the other class which already uses X add also X1 as below:

    [XmlRoot(ElementName = "Container")]
    public class Container
    {
        ...
        [XmlElement(ElementName = "XList")]
        public POCList XList{ get; set; }
        [XmlElement(ElementName = "X1List")]
        public X1List X1List{ get; set; }
        ...
        [XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
        public string Xsi { get; set; }
    }
    

    This should solves the deserialization.

    For primitive types it will be a tricky but you can achieve the same solution using generics with constrains.