Как десериализовать только часть 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-документ
Ответы
Ответ 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, и иметь полный контроль над тем, как он выполняется, вместо того, чтобы полагаться на сериализацию/десериализацию