Как десериализовать дочерние элементы разных типов в список / коллекцию базового типа, который является свойством соответствующего класса - PullRequest
1 голос
/ 13 июля 2020

У меня устаревшая система, которая использует некоторые данные, хранящиеся в формате XML. Формат XML не может быть изменен без возникновения критических проблем (например, инструментов и другого программного обеспечения, которое считывает формат).

XML имеет следующую структуру:

<Things>
    <ThingOne Name="FirstThing">
        <ThingOne Name="FirstChildThing" />
        <ThingTwo Name="SecondChildThing"/>
    </ThingOne>
    <ThingTwo Name="SecondThing">
        <ThingOne Name="ThirdChildThing"  />
        <ThingTwo Name="FourthChildThing" />
    </ThingTwo>
</Things>

В моем сценарии «ThingOne» и «ThingTwo» - это классы, расширяющие «SomeThing». Примерно так:

public abstract class SomeThing 
{
    public int ItemNumber {get; set;}
    public string Name {get; set;}
    public List<SomeThing> ChildThings {get; set;}
    public abstract void InspectChildren();
}

public sealed class ThingOne : SomeThing
{
    public override void InspectChildren()
    {
        /*Iterates Child Things and performs actions*/
    }
}

public sealed class ThingTwo : SomeThing
{
    public override void InspectChildren()
    {
        /*Iterates Child Things and performs actions different from thing one*/
    }
}

А «Вещи» - это класс с набором «SomeThing»:

public sealed class Things
{
    /*Thing One and Thing Two should be deserialized to here*/
    public List<SomeThing> ThingItems {get; set;}
}

Вот такой инспектор вещей:

public static ThingInspector
{
    public static void InspectThings(IEnumerable<SomeThing> thingsToInspect)
    {
        foreach(var thing in thingsToInspect)
        {
            thing.InspectChildren()
        }
    }
}

Однако, когда я использую XmlSerializer для десериализации XML в экземпляр «Вещей», он не берет «ThingOne» и «ThingTwo» и не добавляет их в ThingItems.

Я пытался украсить классы следующие:

public sealed class Things:IList<SomeThing>
{
    /*Thing One and Thing Two should be deserialized to here*/
    [XmlArrayItem(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlArrayItem(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ThingItems {get; set;}

    /*Example doesn't include IList implementation, but it is present and correct*/
}


public abstract class SomeThing 
{
    [XmlIgnore]
    public int ItemNumber {get; set;}

    [XmlAttribute]
    public string Name {get; set;}

    [XmlArrayItem(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlArrayItem(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ChildThings {get; set;}
    
    public abstract void InspectChildren();
}

Десериализованный объект является экземпляром «Вещей», но ThingItems пуст.

Что мне не хватает? Я попытался изменить формат xml, чтобы «ThingOne» и «ThingTwo» были заключены в список «ThingItems», и это нормально работает, но формат xml в производственной системе нельзя изменить без сбоев в работе другого программного обеспечения. это чтение и запись xml данных.

Моя проблема в том, что мне нужно десериализовать дочерние элементы «Вещей» в список в соответствующем классе «Вещи» вместо того, чтобы навязывать структуру объекта элементу XML. Будем признательны за любые рекомендации.

1 Ответ

0 голосов
/ 15 июля 2020

Вы можете десериализовать свой XML, изменив свои типы следующим образом:

public interface IHasThings
{
    List<SomeThing> ChildThings { get; set; }
}

[XmlRoot("Things")]
public class Things : IHasThings
{
    [XmlElement(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlElement(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ChildThings { get; set; } = new List<SomeThing>();
}

public abstract class SomeThing : IHasThings
{
    [XmlIgnore]
    public int ItemNumber { get; set; }

    [XmlAttribute]
    public string Name {get; set;}

    [XmlElement(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlElement(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ChildThings {get; set;} = new List<SomeThing>();

    public abstract void InspectChildren();
}

Примечания:

  • Применяя [XmlElementAttribute] в ваши списки элементы списка будут сериализованы как непосредственные дочерние элементы родительского элемента без вставки дополнительного промежуточного элемента-контейнера.

  • Полиморфизм обрабатывается путем применения [XmlElement(typeof(TThing), ElementName = "ThingName")] , который указывает альтернативный XML элемент, имя для этого c polymorphi c дочерний тип, аналогично тому, как XmlArrayItemAttribute(String, Type) работает для коллекций с элементом контейнера. XmlArrayItem и XmlElement не должны применяться к одному и тому же свойству.

  • Введение IHasThings необязательно, но может упростить обход вашей иерархии с учетом root контейнер и дочерние объекты единообразно.

  • ThingOne и ThingTwo не изменились.

  • Хороший способ отладки проблем в XML десериализация - это создание вручную ожидаемого графа десериализации, а затем его сериализация. Любые расхождения между наблюдаемым и ожидаемым XML скорее всего указывают на то, в чем заключается ваша ошибка десериализации. С вашей моделью данных вы бы увидели дополнительный уровень элемента контейнера <ChildThings> для дочерних коллекций.

Demo fiddle здесь .

...