Как десериализовать только часть XML-документа в C # - PullRequest
27 голосов
/ 16 декабря 2008

Вот вымышленный пример проблемы, которую я пытаюсь решить. Если я работаю в C #, и у меня есть 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, который находится в элементе xml после запуска через XmlSerializer.

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

Спасибо! -Mike

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

Ответы [ 9 ]

37 голосов
/ 12 февраля 2010

Это может быть немного старая тема, но я все равно отправлю. У меня была та же проблема (нужно было десериализовать как 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 (только некоторые детали, которые описывают файл, что может помочь пользователю решить, является ли файл тем, что он хочет загрузить).

7 голосов
/ 14 апреля 2016

Принятый ответ от 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 голосов
/ 16 декабря 2008

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

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

2 голосов
/ 16 декабря 2008

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

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

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

1 голос
/ 27 июля 2009

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

1 голос
/ 16 декабря 2008

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

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

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

0 голосов
/ 06 августа 2009

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

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

0 голосов
/ 27 июля 2009

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

0 голосов
/ 16 декабря 2008

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

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

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

...