Вложенные классы десериализованы из плоского XML

Если у меня есть XML-файл, который выглядит как

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

И я хочу превратить его в класс C#, который выглядит как

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; }
}

Есть ли способ сделать это с помощью простых аннотаций данных (XmlRoot, XmlElementи т. д.) или это мой единственный вариант реализации IXmlSerializable?

РЕДАКТИРОВАТЬ: Обратите внимание, мне только когда-нибудь нужно десериализовать данные. Я получаю XML из стороннего источника и мне не нужно сериализовать Foo обратно в XML (если это облегчает).

1 ответ

Решение

Одним из вариантов является использование XmlAnyElementAttribute, как показано ниже:

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; }
}

Когда XmlSerializer не распознает элемент, он добавляет его к свойству типа XmlElement[], отмеченному XmlAnyElementAttribute. Здесь вы можете обрабатывать свойства Bar. Я использовал отражение там, чтобы показать идею.

Другой вариант - дважды десериализовать и связать Bar с 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

Еще один вариант без вмешательства в десериализацию классов:

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(...);

и если двойная десериализация или рефлексия проблематичны с точки зрения производительности, то вы можете создать суррогатный прокси-класс:

    [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();
Другие вопросы по тегам