IXmlSerializable реализация ReadXml - PullRequest
       42

IXmlSerializable реализация ReadXml

0 голосов
/ 28 ноября 2018

Я пытаюсь получить имя тега XML в свойстве класса при выполнении десериализации XML.Мне нужно имя как свойство, так как несколько тегов XML совместно используют один и тот же класс.XML и связанные с ним классы определены ниже.

У меня есть ответ XML, который я получаю в формате:

<Data totalExecutionTime="00:00:00.0467241">
    <ItemNumber id="1234" order="0" createdDate="2017-03-24T12:07:09.07" modifiedDate="2018-08-29T16:59:19.127">
        <Value modifiedDate="2017-03-24T12:07:12.77">ABC1234</Value>
        <Category id="5432" parentID="9876" itemOrder="0" modifiedDate="2017-03-24T12:16:23.687">The best category</Category>
        ... <!-- like 100 other elements -->
    </ItemNumber>
</Data>

Десериализация выполняется следующим образом:

XmlSerializer serializer = new XmlSerializer(typeof(ItemData));
using (TextReader reader = new StringReader(response))
{
    ItemData itemData = (ItemData)serializer.Deserialize(reader);
}

И класс для верхнего уровня, ItemData:

[Serializable]
[XmlRoot("Data")]
public class ItemData
{
    [XmlAttribute("totalExecutionTime")]
    public string ExecutionTime { get; set; }

    [XmlElement("ItemNumber", Type = typeof(ItemBase))]
    public List<ItemBase> Items { get; set; }
}

ItemBase определяется как:

[Serializable]
public class ItemBase
{
    [XmlElement("Value")]
    public virtual ItemProperty ItemNumber { get; set; } = ItemProperty.Empty;

    [XmlElement("ItemName")]
    public virtual ItemProperty Category { get; set; } = ItemProperty.Empty;

    ... // like 100 other properties
}

И, наконец, ItemProperty:

public class ItemProperty : IXmlSerializable
{
    public static ItemProperty Empty { get; } = new ItemProperty();

    public ItemProperty()
    {
        this.Name = string.Empty;
        this.Value = string.Empty;
        this.Id = 0;
        this.Order = 0;
    }

    public string Name { get; set; }

    [XmlText] // no effect while using IXmlSerializable
    public string Value { get; set; }

    [XmlAttribute("id")] // no effect while using IXmlSerializable
    public int Id { get; set; }

    [XmlAttribute("itemOrder")] // no effect while using IXmlSerializable
    public int Order { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();

        string name = reader.Name;
        this.Name = name;

        string val = reader.ReadElementString();
        this.Value = val;

        if (reader.HasAttributes)
        {
            string id = reader.GetAttribute("id");
            this.Id = Convert.ToInt32(id);

            string itemOrder = reader.GetAttribute("itemOrder");
            this.Order = Convert.ToInt32(itemOrder);

            string sequence = reader.GetAttribute("seq");
            this.Sequence = Convert.ToInt32(sequence);
        }

        // it seems the reader doesn't advance to the next element after reading
        if (reader.NodeType == XmlNodeType.EndElement && !reader.IsEmptyElement)
        {
            reader.Read();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new NotImplementedException();
    }
}

Смысл реализации интерфейса IXmlSerializable заключается в том, что в конечном итоге мне нужно имя тега XML, который хранится как ItemProperty, и эта информация не захватывается при использовании атрибутов класса / свойства XML.Я полагаю, что это так, поскольку атрибуты определяют, какой класс использовать для десериализации, и обычно каждый тег XML имеет связанный класс.Я не хочу идти в этом направлении, так как в ответе может быть так много разных тегов, и все они имеют одинаковые атрибуты.Следовательно, класс ItemProperty.

Я также пытался передать имя тега через параметризованный конструктор в ItemProperty и установить там свойство name, но когда выполняется десериализация, он использует значение по умолчанию.конструктор, а затем устанавливает значения свойств, так что это не вариант.

Отражение также не работает, поскольку класс всегда ItemProperty и поэтому не имеет уникального имени.

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

Я открыт для любого способа решения этой проблемы,но я уверен, что это влечет за собой реализацию IXmlSerializable.ReadXml().Я знаю, что задача XmlReader состоит в том, чтобы прочитать весь XML и продвинуть читателя до конца текста, но мне немного неясно, как это сделать.

TLDR: Как правильно реализовать IXmlSerializable.ReadXml() при записи имени тега XML, значения тега и всех атрибутов в свойства класса?

Редактировать: с помощьюобновленный метод ReadXml, я получаю все данные, необходимые на уровне ItemProperty, но в классе ItemData список Items содержит только один элемент.Я предполагаю, потому что я не продвигаю читателя должным образом.

1 Ответ

0 голосов
/ 05 декабря 2018

Из документации для IXmlSerializable.ReadXml(XmlReader):

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

Ваш ReadXml() может быть изменен для соответствия следующим требованиям:

public void ReadXml(XmlReader reader)
{
    reader.MoveToContent();

    this.Name = reader.LocalName; // Do not include the prefix (if present) in the Name.

    if (reader.HasAttributes)
    {
        var id = reader.GetAttribute("id");
        if (id != null)
            // Since id is missing from some elements you might want to make it nullable
            this.Id = XmlConvert.ToInt32(id);

        var order = reader.GetAttribute("itemOrder");
        if (order != null)
            // Since itemOrder is missing from some elements you might want to make it nullable
            this.Order = XmlConvert.ToInt32(order);

        string sequence = reader.GetAttribute("seq");
        //There is no Sequence property?
        //this.Sequence = Convert.ToInt32(sequence);
    }

    // Read element value.
    // This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
    // thus there is no need for an additional Read()
    this.Value = reader.ReadElementContentAsString();
}

Примечания:

  1. Вы звоните ReadElementString() чья документация гласит :

    Мы рекомендуем использовать метод ReadElementContentAsString() для чтения текстового элемента.

    Как и предполагалось, я изменил ваш ReadXml(), чтобы использовать этот метод.В свою очередь, его документация сообщает :

    Этот метод считывает начальный тег, содержимое элемента и перемещает считыватель за тегом конечного элемента.

    Таким образом, этот метод должен оставить XmlReader в точном соответствии с требованиями ReadXml(), обеспечивая правильное продвижение читателя.

  2. Атрибуты XML каждого элемента ItemPropertyдолжен быть обработан до того, как будет прочитано содержимое этого элемента, так как чтение содержимого продвигает читателя после начала элемента - и его атрибутов.

  3. Утилиты из XmlConvert класс должен использоваться для анализа и форматирования XML-примитивов, чтобы числовые значения и значения даты / времени не были ошибочно локализованы.

  4. Возможно, вы не хотите включать префикс пространства имен (если есть)) в свойстве Name.

Демонстрационная скрипка здесь .

...