2

I am trying to deserialize an XML file with the following structure:

<file:Process xmi:version="2.0"
    xmlns:xmi="http://www.omg.org/XMI"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:MapDB="http://www.dfdse.org/mapdb"
    xmlns:MapDC="http://www.dfdse.org/mapdc"
    xmlns:file="platform:/resource/org.dfdse.model/model/File.xsd">
    <node Name="ELMap">
        <nodeData xsi:type="MapDB:DBMapper">
            <Input name="rec_att">
                <Entries name="cod_rapporto" />
                <Entries name="sisalime"/>
            </Input>
            <Input name="rr_orig_rapp">
                <Entries name="cod_rapporto" />
                <Entries name="dt_origination"/>
                <Entries name="rr"/>
            </Input>
            <Output name="table3">
                <Entries name="cod_rapporto" />
                <Entries name="sisalime" />
                <Entries name="dt_origination" />
                <Entries name="dt_origination_ifrs9" />
            </Output>
        </nodeData>
    </node>
    <node Name="Map">
        <nodeData xsi:type="MapDC:DCMapper">
            <outputDC size="INTERMEDIATE">
                <mapperEntries name="id_ordine" />
                <mapperEntries name="id_cliente" />
            </outputDC>
            <outputDC sizeState="INTERMEDIATE">
                <mapperEntries name="id_spedizione" />
                <mapperEntries name="id_ordine" />
                <mapperEntries name="indirizzo" />
            </outputDC>
            <inputDC sizeState="INTERMEDIATE">
                <mapperEntries name="maxIdOrdine" />
            </inputDC>
        </nodeData>
    </node>
</file:Process>

The issue is that I cannot get any data from the nodeData element. The problem seems to be that the same nodeData element can have two different types, defined in two different namespaces (MapDB and MapDC).

I tried to model the C# classes like this:

[XmlRoot("Process", Namespace = "platform:/resource/org.dfdse.model/model/File.xsd")]
public class Process 
{
    [XmlElement("node", Namespace = "")]
    public List<Node> Nodes { get; set; }
}

public class Node 
{
    [XmlAttribute("Name")]
    public string Name { get; set; }
    
    [XmlElement(ElementName = "nodeData", Type = typeof(DBMapper), Namespace = "http://www.dfdse.org/mapdb")]
    [XmlElement(ElementName = "nodeData", Type = typeof(DCMapper), Namespace = "http://www.dfdse.org/mapdc")]
    public NodeBase NodeData { get; set; }
}

[XmlInclude(typeof(DBMapper))]
[XmlInclude(typeof(DCMapper))]
public class NodeBase 
{ }

//[XmlType(TypeName = "DBMapData", Namespace = "http://www.dfdse.org/mapdb")]
[XmlRoot("nodeData", Namespace = "http://www.dfdse.org/mapdb")]
public class DBMapper : NodeBase 
{
    [XmlElement("Input", Namespace = "")]
    public List<Input> Input { get; set; }
}

//[XmlType(TypeName = "MapperData", Namespace = "http://www.dfdse.org/mapdc")]
[XmlRoot("nodeData", Namespace = "http://www.dfdse.org/mapdc")]
public class DCMapper : NodeBase 
{
    [XmlElement("outputDC", Namespace = "")]
    public List<OutputDC> OutputDC { get; set; }
}

public class Input 
{
    [XmlAttribute("name")]
    public string Name { get; set; }
}

public class OutputDC 
{
    [XmlAttribute("size")]
    public string Size { get; set; }
}

public class Program 
{
    static void Main(string[] args) 
    {
        var xmlFilePath = @"file.xml";

        var serializer = new XmlSerializer(typeof(Process),
                                           new XmlRootAttribute("Process") 
                                               {
                                                   Namespace = "platform:/resource/org.dfdse.model/model/File.xsd"
                                               });

        using FileStream fileStream = new FileStream(xmlFilePath, FileMode.Open);
        var obj = (Process)serializer.Deserialize(fileStream);
    }
}

However, deserialization does not work correctly, and the NodeData object is not populated. What is the correct way to handle deserialization of an XML element (nodeData) that can have multiple types (specified with xsi:type) and belong to different namespaces?

1
  • Demo fiddle here. Commented Dec 6 at 6:45

1 Answer 1

6

You have a few problems with your deserialization code, lets take them in order:

  1. XmlSerializer supports two mechanisms for indicating polymorphic subtype: specification via an xsi:type attribute, or specification via element name. Your XML is using the xsi:type mechanism, and by adding XmlInclude attributes to NodeBase you've enabled it. However, by adding two [XmlElement] attributes to NodeData with different Type values, you've overridden that choice and selected element name polymorphism instead:

    [XmlElement(ElementName = "nodeData", Type = typeof(DBMapper), Namespace = "http://www.dfdse.org/mapdb")]
    [XmlElement(ElementName = "nodeData", Type = typeof(DCMapper), Namespace = "http://www.dfdse.org/mapdc")]
    

    You should remove the Type values and the doubled attributes and just use a single attribute:

    [XmlElement(ElementName = "nodeData")]
    public NodeBase NodeData { get; set; }
    

    For further details on both mechanisms, see Using XmlSerializer to serialize derived classes.

  2. The value of the xsi:type attribute is namespacePrefix:typename. Since the xsi:type for DBMapper is "MapDB:DBMapper" you must use "DBMapper" not "nodeName" for its XmlTypeAttribute.Name, setting the namespace to the one indicated by the MapDB prefix:

    [XmlType("DBMapper", Namespace = "http://www.dfdse.org/mapdb")]
    public class DBMapper : NodeBase 
    

    And similarly you must use "DCMapper" for DCMapper:

    [XmlRoot("DCMapper", Namespace = "http://www.dfdse.org/mapdc")]
    public class DCMapper : NodeBase 
    
  3. Since your derived types DBMapper and DCMapper are assigned to an XML namespace, you must assign the base type to one also -- even though that namespace never actually appears in the XML. If you don't, XmlSerializer will throw an obscure and unhelpful exception. It doesn't matter what the namespace actually is -- it could be an empty string -- but it does need to be set explicitly:

    [XmlInclude(typeof(DBMapper))]
    [XmlInclude(typeof(DCMapper))]
    [XmlType("NodeBase", Namespace = "")]
    public abstract class NodeBase; // I recommend abstract here since you never actually want to construct one of these.
    

    Demo of the obscure exception here.

  4. You don't actually need to pass a new XmlRootAttribute("Process") { } to your XmlSerializer constructor, you can just construct it normally:

    var serializer = new XmlSerializer(typeof(Process));
    

    But if you ever do need to construct an XmlSerializer using a constructor other than the default constructor, you must statically cache and reuse it to avoid a severe memory leak. For details see Marc Gravell's answer to Memory Leak using StreamReader and XmlSerializer.

Putting it all together, your classes should look like:

[XmlRoot("Process", Namespace = "platform:/resource/org.dfdse.model/model/File.xsd")]
public class Process 
{
    [XmlAttribute("version", Namespace = "http://www.omg.org/XMI")]
    public string XmlVersion { get; set; } = "2.0";
    
    [XmlElement("node", Namespace = "")]
    public List<Node> Nodes { get; set; } = new();
}

public class Node 
{
    [XmlAttribute("Name")]
    public string Name { get; set; }
    
    [XmlElement(ElementName = "nodeData")]
    public NodeBase NodeData { get; set; }
}

[XmlInclude(typeof(DBMapper))]
[XmlInclude(typeof(DCMapper))]
[XmlType("NodeBase", Namespace = "")]
public abstract class NodeBase; // I recommend abstract here since you never actually want to construct one of these.

[XmlType("DBMapper", Namespace = "http://www.dfdse.org/mapdb")]
public class DBMapper : NodeBase 
{
    [XmlElement("Input", Namespace = "")]
    public List<Input> Input { get; set; }
}

[XmlType("DCMapper", Namespace = "http://www.dfdse.org/mapdc")]
public class DCMapper : NodeBase 
{
    [XmlElement("outputDC", Namespace = "")]
    public List<OutputDC> OutputDC { get; set; }
}

public class Input {
    [XmlAttribute("name")]
    public string Name { get; set; }
}

public class OutputDC {
    [XmlAttribute("size")]
    public string Size { get; set; }
}

Incidentally, a good way to debug problems with deserialization is to create an instance of your data model in memory, serialize it, and compare your generated XML with the XML you need to deserialize. Any differences likely point towards the causes of your problems.

Demo fiddle here.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot, you're a genius! It works now. Also, thanks for the final tip. honestly hadn’t thought of that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.