0

I am attempting to deserialize some XML which I am not responsible for producing. It has a monolithic node and various branches for several modules. The problem is each module may have similar sub nodes that have different nodes and attributes but share the same name. These similar nodes are not namespaced. In abstract it will look something like this as the target type.

<Root>
    <Module1>
         <Node SomeAttribute="123" />
    </Module1>
    <Module2>
         <Node SomeOtherAttribute="Something" />
    </Module2>
</root>

I have seem various suggestions to annotated my pocos with a namespace to avoid the resulting exception when I try to construct a XmlSerializer using the Root type that has both Module1 and Module2 as members.

System.InvalidOperationException : Types 'Root.Module1.Item1' and 'Root.Module1.Item2' both use the XML type name, 'Item', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.

I think if using System.Text.Json I wouldn't have this problem as the type is decided by the poco class structure not my the name of the node being deserialized.

Is there a way to deserialize this object in it's monolithic form, perhaps by annotating the Module1.Node and Module1.Node poco class with decorators?

I couldn't find the relevant decorators when I tried. I did succeed in stopping the XmlSerializer constructor exception but it stopped recognising the Node types and was unable to deserialize either.

My next step will to make separate XmlSerializer instances for each Module and try and see if I can do away with the Root object which felt inefficient anyway.

Here is an example of the setup in fiddle: https://dotnetfiddle.net/0twN0O

Craig.C
  • 561
  • 4
  • 17
  • Please show the line of code that you are using to "deserialize" the xml. – rabbit Mar 02 '22 at 10:45
  • 1
    `XmlSerializer` is a complex and oftentimes annoying beast. When it's giving trouble, it's worth seeing if you can ditch it entirely in favor of writing your own parsing with explicit `XElement` calls, which give very convenient typed access. Depending on how big and complicated your markup is this may be some work, but then so is figuring out how to configure `XmlSerializer` so it likes things just the way they are. – Jeroen Mostert Mar 02 '22 at 11:03
  • Hi @rabbit, the exception is the XmlSerializer constructor e.g. new XmlSerializer(typeof(Root), new XmlRootAttribute("Root")); – Craig.C Mar 02 '22 at 12:03
  • The pocos are much how @d-a mention in his answer below. I guess I can set up a fiddle. – Craig.C Mar 02 '22 at 12:06

3 Answers3

1

I have a solution for you, but it will work only if you will fix your XML before using it (for example 123 should be with "123").

public class Node
{
    [XmlAttribute]
    public string SomeOtherAttribute { get; set; }

    [XmlAttribute]
    public int SomeAttribute { get; set; }
}

public class Module
{
    public Node Node { get; set; }
}

[XmlRoot("Root")]
public class OrderedItem
{
    [XmlElement("Module1")]
    public Module Module1 { get; set; }
    [XmlElement("Module2")]
    public Module Module2 { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        string xml = @"<Root>
                        <Module1>
                             <Node SomeAttribute = ""123"" /> 
                         </Module1> 
                         <Module2>
                              <Node SomeOtherAttribute = ""Something"" /> 
                          </Module2 >
                      </Root>";

        XmlSerializer serializer = new XmlSerializer(typeof(OrderedItem));
        using (TextReader reader = new StringReader(xml))
        {
            var result = (OrderedItem)serializer.Deserialize(reader);
        }

    }
}
D A
  • 1,724
  • 1
  • 8
  • 19
  • Sorry RE int/string. I don't know XML too well, I thought I should write an in without speech marks. My problem with this is that my Nodes are actually quite complex and have different members and this will give me trouble when trying to access a Node, knowing which props are going to me always null. I guess I could have a Node with two interfaces and cast to the relevant one once deserialized. – Craig.C Mar 02 '22 at 11:58
  • 1
    I don't know how I can help you forward. I've answered your question based on the info you provided... – D A Mar 02 '22 at 12:03
  • This is certainly a useful workaround for basic types. I was hoping I could keep my types separate and avoid the exception with class decorators but I think this isn't going to be possible with XmlSerializer. – Craig.C Mar 02 '22 at 12:09
  • Hi @d-a, I made a fiddle https://dotnetfiddle.net/CLiFjc combining your idea with interfaces. If I could find a way to hide the derived type and just have a property of the right interface I think this would be a really workable solution. – Craig.C Mar 02 '22 at 13:03
0

This is a little extension on @d-a's answer using interfaces to help keep the objects separate from the consumers point of view.

The answer to the following was super useful: XML serialization of interface property

I don't like the public method of the concrete type but I struggled to make private and deserialize with the ISerializable interface.

Might still have a go at another Serialiser to see how it goes https://github.com/ExtendedXmlSerializer/home.

using System.IO;
using System.Xml;
using System.Xml.Serialization;

[XmlType]
public class Node : Module1.Node1, Module2.Node2
{
    [XmlAttribute]
    public string SomeOtherAttribute { get; set; }

    [XmlAttribute]
    public int SomeAttribute { get; set; }
}

public class Module1
{   [XmlElement(ElementName="Node")]
    public Node _node { get; set; }
    [XmlIgnore]
    public Node1 Node { get {return (Node1)_node ;} }
    
    public interface Node1 {
        public int SomeAttribute { get; set; }
    }

}

public class Module2
{   [XmlElement(ElementName="Node")]
    public Node _node { get; set; }
    [XmlIgnore]
    public Node2 Node { get {return (Node2)_node ;} }
    
    public interface Node2 {
        public string SomeOtherAttribute { get; set; }
    }
}

[XmlRoot("Root")]
public class OrderedItem
{
    [XmlElement("Module1")]
    public Module1 Module1 { get; set; }
    [XmlElement("Module2")]
    public Module2 Module2 { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        string xml = @"<Root>
                        <Module1>
                             <Node SomeAttribute = ""123"" /> 
                         </Module1> 
                         <Module2>
                              <Node SomeOtherAttribute = ""Something"" /> 
                          </Module2 >
                      </Root>";

        XmlSerializer serializer = new XmlSerializer(typeof(OrderedItem));
        using (TextReader reader = new StringReader(xml))
        {
            var result = (OrderedItem)serializer.Deserialize(reader);
            System.Console.Out.WriteLine(result.Module1.Node.SomeAttribute);
            System.Console.Out.WriteLine(result.Module2.Node.SomeOtherAttribute);
        }

    }
}

https://dotnetfiddle.net/3AYdVT

Craig.C
  • 561
  • 4
  • 17
0

So long as you make the poco classes names unique. There is no need for the property names to be unique. Therefore, the types of Node should be unique but they the members of this unique type may both be called Node.

https://dotnetfiddle.net/0twN0O

using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class Module1{
    public Node1 Node { get; set; }
    public class Node1 {
        [XmlAttribute]
        public int SomeAttribute { get; set; }
    }
}

public class Module2
{   
    public Node2 Node { get; set; }
    public class Node2 {
        [XmlAttribute]
        public string SomeOtherAttribute { get; set; }
    }
}

[XmlRoot("Root")]
public class OrderedItem
{
    [XmlElement("Module1")]
    public Module1 Module1 { get; set; }
    [XmlElement("Module2")]
    public Module2 Module2 { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        string xml = @"<Root>
                        <Module1>
                             <Node SomeAttribute = ""1232"" /> 
                         </Module1> 
                         <Module2>
                              <Node SomeOtherAttribute = ""Something"" /> 
                          </Module2 >
                      </Root>";

        XmlSerializer serializer = new XmlSerializer(typeof(OrderedItem));
        using (TextReader reader = new StringReader(xml))
        {   
            var result = (OrderedItem)serializer.Deserialize(reader);
            System.Console.Out.WriteLine(result.Module1.Node.SomeAttribute);
            System.Console.Out.WriteLine(result.Module2.Node.SomeOtherAttribute);
        }

    }
}
Craig.C
  • 561
  • 4
  • 17