Подтвердить что ты не робот

Как десериализовать только часть XML-документа в С#

Вот фиктивный пример проблемы, которую я пытаюсь решить. Если я работаю на С# и имею XML следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <SalesPerson>
    <Company>Acme Sales</Company>
    <Position>
       <Salary>
          <Amount>1000</Amount>
          <Unit>Dollars</Unit>
    ... and on... and on....
  </SalesPerson>
</Cars>

XML внутри SalesPerson может быть очень длинным, мегабайт в размере. Я хочу десериализовать тег , но не десериализовать XML-элемент SalesPerson вместо сохранения его в необработанном виде "для последующего использования".

По сути, я хотел бы иметь возможность использовать это как представление XML объектов.

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }

    public Stream SalesPerson { get; set; }
}

public class Car
{
    [System.Xml.Serialization.XmlElementAttribute("StockNumber")]
    public string StockNumber{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Make")]
    public string Make{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Model")]
    public string Model{ get; set; }
}

где свойство SalesPerson объекта Cars будет содержать поток с необработанным xml, который находится внутри <SalesPerson> xml после запуска через XmlSerializer.

Можно ли это сделать? Могу ли я выбрать только десериализацию "части" XML-документа?

Спасибо! -Mike

p.s. Пример xml, украденный из Как десериализовать XML-документ

4b9b3361

Ответ 1

Это может быть немного старый поток, но я все равно отправлю. У меня была та же проблема (необходимо было десериализовать, например, 10 КБ данных из файла с более чем 1 МБ). В основном объекте (который имеет InnerObject, который должен быть десериализатором), я реализовал интерфейс IXmlSerializable, а затем изменил метод ReadXml.

В качестве ввода мы имеем xmlTextReader, первая строка должна читать до тега XML:

reader.ReadToDescendant("InnerObjectTag"); //tag which matches the InnerObject

Затем создайте XMLSerializer для типа объекта, который мы хотим десериализовать и десериализировать.

XmlSerializer   serializer = new XmlSerializer(typeof(InnerObject));

this.innerObject = serializer.Deserialize(reader.ReadSubtree()); //this gives serializer the part of XML that is for  the innerObject data

reader.close(); //now skip the rest 

это сэкономило мне много времени для десериализации и позволяет мне читать только часть XML (только некоторые детали, описывающие файл, которые могут помочь пользователю решить, нужен ли файл для загрузки).

Ответ 2

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

...xmlns=''> was not expected

Это исключение возникло, когда я попытался десериализовать только внутренний элемент аутентификации этого документа xml:

<?xml version=""1.0"" encoding=""UTF-8""?>
<Api>
  <Authentication>                       
      <sessionid>xxx</sessionid>
      <errormessage>xxx</errormessage>                
  </Authentication>
</ApI>

Итак, я создал этот метод расширения как многоразовое решение - предупреждение содержит утечку памяти, см. ниже:

public static T DeserializeXml<T>(this string @this, string innerStartTag = null)
        {
            using (var stringReader = new StringReader(@this))
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = new XmlSerializer(typeof(T), new XmlRootAttribute(innerStartTag));
                    return (T)xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T)new XmlSerializer(typeof(T)).Deserialize(xmlReader);
            }
        }

Обновление 20 марта 2017 года. Как отмечается ниже, проблема с утечкой памяти возникает при использовании одного из конструкторов XmlSerializer, поэтому я решил использовать кэширующее решение, как показано ниже:

    /// <summary>
    ///     Deserialize XML string, optionally only an inner fragment of the XML, as specified by the innerStartTag parameter.
    /// </summary>
    public static T DeserializeXml<T>(this string @this, string innerStartTag = null) {
        using (var stringReader = new StringReader(@this)) {
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute(innerStartTag));
                    return (T) xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T) CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute("AutochartistAPI")).Deserialize(xmlReader);
            }
        }
    }
/// <summary>
///     A caching factory to avoid memory leaks in the XmlSerializer class.
/// See http://dotnetcodebox.blogspot.dk/2013/01/xmlserializer-class-may-result-in.html
/// </summary>
public static class CachingXmlSerializerFactory {
    private static readonly ConcurrentDictionary<string, XmlSerializer> Cache = new ConcurrentDictionary<string, XmlSerializer>();
    public static XmlSerializer Create(Type type, XmlRootAttribute root) {
        if (type == null) {
            throw new ArgumentNullException(nameof(type));
        }
        if (root == null) {
            throw new ArgumentNullException(nameof(root));
        }
        var key = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", type, root.ElementName);
        return Cache.GetOrAdd(key, _ => new XmlSerializer(type, root));
    }
    public static XmlSerializer Create<T>(XmlRootAttribute root) {
        return Create(typeof (T), root);
    }
    public static XmlSerializer Create<T>() {
        return Create(typeof (T));
    }
    public static XmlSerializer Create<T>(string defaultNamespace) {
        return Create(typeof (T), defaultNamespace);
    }
    public static XmlSerializer Create(Type type) {
        return new XmlSerializer(type);
    }
    public static XmlSerializer Create(Type type, string defaultNamespace) {
        return new XmlSerializer(type, defaultNamespace);
    }
}

Ответ 3

Вы можете контролировать, как выполняется ваша сериализация, реализуя интерфейс ISerializable в своем классе. Обратите внимание, что это также подразумевает конструктор с сигнатурой метода (информация SerializationInfo, контекст StreamingContext) и вы можете делать то, что вы просите с этим.

Однако внимательно посмотрите, действительно ли вам нужно делать это с потоковой передачей, потому что, если вам не нужно использовать механизм потоковой передачи, добиться того же самого результата с Linq to XML будет проще и проще в обслуживании в долгосрочной перспективе (ИМО)

Ответ 4

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

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

Я знаю, что это на самом деле не отвечает на ваш вопрос, но я подумал, что хочу выделить альтернативное решение, которое вы можете использовать. Хорошая база данных и соответствующий сопоставитель OR, например .netTiers, NHibernate, или совсем недавно LINQ to SQL/Entity Framework, вероятно, заставят вас работать с минимальными изменениями в остальной части вашей кодовой базы.

Ответ 5

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

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

Это хороший пример того, почему XML не всегда правильный ответ.

Ответ 6

Попробуйте определить свойство SalesPerson как тип XmlElement. Это работает для вывода веб-сервисов ASMX, которые используют XML-сериализацию. Я бы подумал, что это будет работать и на входе. Я ожидаю, что весь элемент <SalesPerson> завершится в XmlElement.

Ответ 7

Вы можете контролировать, какие части класса Cars десериализованы, реализовав интерфейс IXmlSerializable в классе Cars, а затем в методе ReadXml (XmlReader) вы будете читать и десериализовывать элементы Car, но когда вы попадаете в элемент SalesPerson, который вы читаете поддеревом в виде строки, а затем создаете Stream над текстовым контентом с помощью StreamWriter.

Если вы никогда не хотите, чтобы XmlSerializer записывал элемент SalesPerson, используйте атрибут [XmlIgnore]. Я не уверен, что вы хотите, когда сериализуете класс Cars в своем XML-представлении. Вы пытаетесь только предотвратить десериализацию SalesPerson, все еще имея возможность сериализовать XML-представление SalesPerson, представленного Stream?

Возможно, я мог бы привести пример кода, если вы хотите конкретную реализацию.

Ответ 8

Если все, что вы хотите сделать, это разобрать элемент SalesPerson, но сохранить его как строку, вы должны использовать Xsl Transform, а не "Deserialization". Если, с другой стороны, вы хотите проанализировать элемент SalesPerson и только заполнить объект в памяти из всех других элементов, не относящихся к SalesPerson, то Xsl Transform также может быть способом. Если файлы имеют большой размер, вы можете рассмотреть их разделение и использовать Xsl для объединения разных xml файлов, чтобы сбой ввода-вывода SalesPerson возникал только тогда, когда вам это нужно.

Ответ 9

Я бы предложил вам вручную прочитать из Xml, используя любые легкие методы, такие как XmlReader, XPathDocument или LINQ-to-XML.

Когда вам нужно читать только 3 свойства, я полагаю, вы можете написать код, который вручную читается из этого node, и иметь полный контроль над тем, как он выполняется, вместо того, чтобы полагаться на сериализацию/десериализацию