3

If I have a xml file that looks like

<Foo>
  <Name>Some Data</Name>
  <Bar_Data>Other Data</Bar_Data>
  <Bar_MoreData>More Data</Bar_MoreData>
</Foo>

And I want to turn it in to a C# class that looks like

public class Foo
{
    public string Name {get; set; }
    public Bar Bar { get; set; }
}

public class Bar
{
    public string Data { get; set; }
    public string MoreData { get; set; }
}

Is there any way to accomplish this with just simple data annotations (XmlRoot, XmlElement, etc.) or is my only option to implement IXmlSerializable?

EDIT: Note, I only ever need to deserialize the data. I get the XML from a 3rd party source and I do not need to ever serialize Foo back in to XML (if that makes it easier).

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 1) Just to confirm: you're using `XmlSerializer`, right? 2) I don't think this can be done entirely with simple data annotations, but there are still easier methods than implementing `IXmlSerializable` which is quite burdensome. Would those be useful answers? – dbc Aug 07 '15 at 20:11
  • @bdc 1) I am using XmlSerializer right now but I am not tied to it 3) I am open to any solution that does not use a GPL'ed library (I can not use any library that would require me to release my source code, but LGPL or any other OSS that does not require releasing your source is fine) – Scott Chamberlain Aug 07 '15 at 20:19
  • @ScottChamberlain, are any of the options from my answer suitable for your problem or you have other constraints that you did not mention yet? – Vitaliy Tsvayer Aug 08 '15 at 20:25
  • @VitaliyTsvayer Its good and I will likely accept it, I just wanted to give it a few days to not discurage other answres. – Scott Chamberlain Aug 08 '15 at 20:43
  • @ScottChamberlain, indeed here is one more option... – Vitaliy Tsvayer Aug 08 '15 at 22:03

1 Answers1

4

One option is to use XmlAnyElementAttribute as below:

public class Foo
{
    public string Name { get; set; }
    public Bar Bar { get; set; }

    [XmlAnyElementAttribute]
    public XmlElement[] BarElements
    {
        get { return null; }
        set
        {
            Bar = new Bar();
            var barType = Bar.GetType();
            foreach (var prop in value)
                barType.GetProperty(prop.Name.Substring(4)).SetValue(Bar, prop.InnerText);
        }
    }
}

public class Bar
{
    public string Data { get; set; }
    public string MoreData { get; set; }
}

When XmlSerializer does not recognize an element it adds it to property of type XmlElement[] marked by XmlAnyElementAttribute. That is where you can process Bar properties. I used reflection there to show the idea.

Another option is to deserialize twice and connect Bar with Foo:

public class Foo
{
    public string Name { get; set; }
    public Bar Bar { get; set; }
}

[XmlRoot("Foo")]
public class Bar
{
    [XmlElement("Bar_Data")]
    public string Data { get; set; }
    [XmlElement("Bar_MoreData")]
    public string MoreData { get; set; }
}

var foo = (Foo) new XmlSerializer(typeof (Foo)).Deserialize(...);
var bar = (Bar) new XmlSerializer(typeof (Bar)).Deserialize(...);
foo.Bar = bar

Yet another option with no intrusion to classes being deserialized:

public class Foo
{
    public string Name { get; set; }
    public Bar Bar { get; set; }
}

public class Bar
{
    public string Data { get; set; }
    public string MoreData { get; set; }
}

var fooSerializer = new XmlSerializer(typeof (Foo));
fooSerializer.UnknownElement += (sender, e) =>
{
    var foo = (Foo) e.ObjectBeingDeserialized;
    if(foo.Bar == null)
        foo.Bar = new Bar();
    var propName = e.Element.Name.Substring(4);
    typeof(Bar).GetProperty(propName).SetValue(foo.Bar, e.Element.InnerText);
};

var fooInstance = fooSerializer.Deserialize(...);

and if double deserialization or reflection is problematic performance wise, then you could create a surrogate proxy class:

    [XmlRoot("Foo")]
    public class FooSurrogate
    {
        public string Name { get; set; }
        public string Bar_Data { get; set; }
        public string Bar_MoreData { get; set; }

        public Foo ToFoo()
        {
            return new Foo
            {
                Name = Name,
                Bar = new Bar
                {
                    Data = Bar_Data,
                    MoreData = Bar_MoreData
                }
            };
        }
    }

    var seializer = new XmlSerializer(typeof (FooSurrogate));
    var foo = ((FooSurrogate) seializer.Deserialize(...)).ToFoo();
Vitaliy Tsvayer
  • 765
  • 4
  • 14