Фильтр XmlSerializer по атрибуту - PullRequest
1 голос
/ 24 апреля 2020

При десериализации XML в класс сущности с использованием XmlSerializer возможно ли фильтровать по атрибуту? Например, допустим, у меня есть элемент, который может быть типа "a" или типа "b". Я хочу десериализовать все элементы, но только те, которые относятся к типу "a".

Мне это нужно, потому что моя реальная ситуация заключается в том, что наша конечная точка получает очень большие XML-файлы (некоторые могут превышать 100 МБ) с сотнями тысяч тегов типа <item>, но мне нужны только некоторые из них - типа "а". Я хочу избежать выделения для остальных (включая их дочерние теги, которых не мало).

Пример XML:

<root>
  <item type="a"/>
  <item type="a"/>
  <item type="b"/>
  <item type="c"/>
</root>

Сущности:

[XmlRoot("root")]
public class Root {
    [XmlElement("item")]
    public Item[] Items { get; set; }
}
public class Item  {
    [XmlAttribute("type")]
    [DeserializeIfValueIs("a")] // <-- Is there something like this?
    public string Type { get; set; }
}

Код:

var serializer = new XmlSerializer(typeof(Root));
var dto = (Root) serializer.Deserialize(XmlReader.Create("input.xml"));
// Show the results - {"Items":[{"Type":"a"},{"Type":"a"},{"Type":"b"},{"Type":"c"}]}
Console.WriteLine(JsonConvert.SerializeObject(dto));

Как заставить его выделять объекты только для элементов типа "a"?

Обязательное примечание: это не проблема XY и не преждевременная оптимизация. Мы определили, что нам нужно улучшить производительность в этом с профилированием и так далее. Кроме того, фильтрация значений после десериализации не помогает - к тому времени распределения уже были произведены и их придется собирать мусором.

1 Ответ

2 голосов
/ 24 апреля 2020

Это возможно, если мы сами выполняем процесс десериализации (по крайней мере, для класса root)

. Напомню, что предоставленный вами контент XML недостаточен для запуска модульных тестов. , так что это очень базовая c реализация, которая, однако, должна работать на вас напрямую или просто немного подправить кое-где.

Прежде всего, мы изменим наш класс Item XML атрибут сериализации в root. В ближайшее время будет дан ответ «Почему».

[XmlRoot("item")]
public class Item
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("prop1")]
    public int Prop1 { get; set; }
}

Я также добавил простое целочисленное свойство, чтобы доказать, что десериализация работает, как и ожидалось.

Я также изменил содержимое XML для соответствия новому типу, для тестирования.

<root>
  <item type="b">
    <prop1>5</prop1>
  </item>
  <item type="a">
    <prop1>5</prop1>
  </item>
  <item type="a">
    <prop1>5</prop1>
  </item>
  <item type="b">
    <prop1>5</prop1>
  </item>
  <item type="c">
    <prop1>5</prop1>
  </item>
</root>

А теперь появился класс Root, который прямо сейчас реализует IXmlSerializable:

[XmlRoot("root")]
public class Root : IXmlSerializable
{
    [XmlElement("item")]
    public Item[] Items { get; set; }

    // These two methods are not implemented for you need to deserialize only,
    // and because you haven't provided the schema for your XML content
    System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() { throw new NotImplementedException(); }
    void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { throw new NotImplementedException(); }

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
    {
        // The element is <root> when here for the first time.

        // Maintain a list to keep items with type "a"
        List<Item> typeAItems = new List<Item>();

        // Create a serializer for the type Item
        XmlSerializer deserializer = new XmlSerializer(typeof(Item));

        while (reader.Read())
        {
            // The code is self explanatory.
            // Skip() will help omitting unnecessary reads
            // if we are not interested in the Item
            if (reader.IsStartElement() && reader.Name == "item")
            {
                if (reader.GetAttribute("type") == "a")
                {
                    // This works, and deserializes the current node
                    // into an Item object. When the deserialization
                    // is completed, the reader is at the beginning
                    // of the next <Item> element
                    typeAItems.Add((Item)deserializer.Deserialize(reader));
                }
                else
                {
                    // skip element with all its children
                    reader.Skip();
                }
            }
            else
            {
                // skip element with all its children
                reader.Skip();
            }
        }
        Items = typeAItems.ToArray();
    }
}

Лог десериализации c сохранен то же самое, как новый XmlSerializer (typeof (Root)). Deserialize ().

Остальное .. для проверки.

...