3

So if I have 3 different files that contain extremely long and complex xml (which i've abbreviated here) but just the root element name is different (not really random but I'd rather not duplicate classes for the same xml):

File 1

<?xml version="1.0"?>
<somerootname1>
  <car>
    <make>honda</make>
    <model>civic</model>
  </car>
</somerootname1>

File 2

<?xml version="1.0"?>
<somerootname2>
  <car>
    <make>honda</make>
    <model>civic</model>
  </car>
</somerootname2>

File 3

<?xml version="1.0"?>
<somerootname3>
  <car>
    <make>honda</make>
    <model>civic</model>
  </car>
</somerootname3>

Because XmlRootAttribute has AllowMultiple=false I can't create a single class to represent the previous xml.

Duplicate 'XmlRootAttribute' attribute

[XmlRoot(ElementName="somerootname1")]
[XmlRoot(ElementName="somerootname2")]
public class Root
{
  public Automobile Car { get; set; }

}

public class Automobile 
{
  public string Make { get; set; }
  public string Model { get; set; }
}

I thought I might be able to be sneaky and grab just the element I need and deserialize it:

var xDoc = XDocument.Parse("myfile.xml");

var xCar = xDoc.Root.Descendants()
  .FirstOrDefault(d => d.Name.LocalName.Equals("car"))

var serializer = new XmlSerializer(typeof(Automobile))
using (var reader = xml.CreateReader())
{
  result = (Automobile)serializer.Deserialize(reader);
}

However that results in an InvalidOperationException:

There is an error in XML document (0, 0).

With an InnerException:

<car> was not expected.

It seems really hacky to either; rename the root element and then deserialize OR take the element and prefix the <?xml> tag. Any other suggestions?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150

2 Answers2

1

I did not come up with this but just tweaked it some, the original is from: Deserialize XML To Object using Dynamic

Not sure if it is exactly what you need, but it would allow you to grab the values from the xml and put them into your class. It can be adjusted for arrays or collections as well, if there were multiple cars in one xml doc.

You will need to add:

using System.Dynamic;
using System.Xml.Linq;


    protected Car ParseXml(Car NewCar) {          
        string xml = "<?xml version=\"1.0\"?><somerootname1>  <car>    <make>honda</make>    <model>civic</model>  </car></somerootname1>";            
        dynamic car = DynamicXml.Parse(xml);            
        NewCar.Make = car.car.make;
        NewCar.Model = car.car.model;
        return NewCar;
    }

[Serializable]
public partial class Car
{
    public Car() { }

    public string Make { get; set; }

    public string Model { get; set; }
}


public class DynamicXml : DynamicObject
{
    XElement _root;
    private DynamicXml(XElement root)
    {
        _root = root;
    }

    public static DynamicXml Parse(string xmlString)
    {
        return new DynamicXml(XDocument.Parse(xmlString).Root);
    }

    public static DynamicXml Load(string filename)
    {
        return new DynamicXml(XDocument.Load(filename).Root);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;

        var att = _root.Attribute(binder.Name);
        if (att != null)
        {
            result = att.Value;
            return true;
        }

        var nodes = _root.Elements(binder.Name);
        if (nodes.Count() > 1)
        {
            result = nodes.Select(n => new DynamicXml(n)).ToList();
            return true;
        }

        var node = _root.Element(binder.Name);
        if (node != null)
        {
            if (node.HasElements)
            {
                result = new DynamicXml(node);
            }
            else
            {
                result = node.Value;
            }
            return true;
        }

        return true;
    }
}
Community
  • 1
  • 1
Popo
  • 2,402
  • 5
  • 33
  • 55
  • Because the xml has hundreds of nodes it would be easier to do this with Linq2Xml then update the ParseXml method (which for me would appear more convoluted). – Erik Philips Feb 28 '14 at 17:50
  • @ErikPhilips That would make sense if you are dealing with huge xml messages with many different node names. I parse a lot of xml, but have built classes from xsds, so the xml deserializes nicely into my objects. If I get extra elements in my xml they just get overlooked during deserilization. Anyway, like I said, I was not sure if it would work for you. – Popo Feb 28 '14 at 18:08
1

You have extracted the <car> node, but you're still deserializing from the document's root node. That's why the exception suggests that it's expecting the <car> node. The following works for me:

[XmlRoot("car")]
public class Automobile
{
    [XmlElement("make")]
    public string Make { get; set; }
    [XmlElement("model")]
    public string Model { get; set; }
}

Deserialize from <car>:

var xCar = xDoc.Root.Descendants()
    .FirstOrDefault(d => d.Name.LocalName.Equals("car"));

var serializer = new XmlSerializer(typeof(Automobile));
using (var reader = xCar.CreateReader())
{
    var result = (Automobile)serializer.Deserialize(reader);
    Console.WriteLine(result.Make);
    Console.WriteLine(result.Model);
}
Fung
  • 3,508
  • 2
  • 26
  • 33
  • 1
    Due to my lack of knowledge about XML namespaces, I found out it was the name spaces that was also causing an addition error with the same description :(. – Erik Philips Mar 03 '14 at 18:03