2008-12-15 6 views
23

Oto fikcyjny przykład problemu, który próbuję rozwiązać. Jeśli pracuję w C# i mają XML jak poniżej:Jak deserializować tylko część dokumentu XML w języku C#

<?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 wewnątrz sprzedawca może być bardzo długi, megabajtów. Chcę deserializować tag, , ale nie deserializacji elementu SalesPerson XML zamiast utrzymywania go w surowej postaci "na później".

Zasadniczo chciałbym móc użyć tego jako reprezentacji obiektów 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; } 
} 

których właściwość Salesperson na obiekcie wozy zawiera strumień z surowego XML, który jest w sprzedawca > elementu < xml, po czym przepuszczano przez XmlSerializer.

Czy to można zrobić? Czy mogę wybrać tylko deserializację "części" dokumentu xml?

Dzięki! -Mike

p.s. przykład xml ukradziony od How to Deserialize XML document

Odpowiedz

30

To może być trochę stary wątek, ale mimo to opublikuję. Miałem ten sam problem (potrzebne do deserializacji jak 10kb danych z pliku, który miał więcej niż 1 MB). W głównym obiekcie (który ma obiekt InnerObject, który wymaga deserializera) zaimplementowałem interfejs IXmlSerializowalny, a następnie zmieniono metodę ReadXml.

Mamy XmlTextReader jako wejście, pierwsza linia jest czytać do znacznika XML:

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

następnie utworzyć XmlSerializer dla typu obiektu, który chcemy deserializowania i deserializować go

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 

zaoszczędziło mi to wiele czasu na deserializację i pozwala mi odczytać tylko część XML (tylko niektóre szczegóły opisujące plik, co może pomóc użytkownikowi w podjęciu decyzji, czy plik jest tym, co chce załadować).

+0

Doskonałe rozwiązanie, znalazłem jednak, że muszę ustawić xml root fragmentu, aby uniknąć wyjątku z wewnętrznym wyjątkiem mówiącym ... xmlns = ''> nie był oczekiwany. Dodałem kolejną odpowiedź na moje rozwiązanie, ze względu na ograniczenia długości komentarza. –

3

Możesz kontrolować sposób przeprowadzania serializacji poprzez implementację interfejsu ISerializable w klasie. Zauważ, że będzie to również implikować konstruktor z sygnaturą metody (informacje o SerializationInfo, kontekście StreamingContext) i upewnić się, że możesz zrobić to, o co prosisz.

Warto jednak dokładnie sprawdzić, czy naprawdę trzeba to zrobić z transmisją strumieniową, ponieważ jeśli nie trzeba korzystać z mechanizmu przesyłania strumieniowego, osiągnięcie tego samego z Linq do XML będzie łatwiejsze i prostsze utrzymywać na dłuższą metę (IMO)

1

Zazwyczaj deserializacja XML to propozycja typu "wszystko albo nic" po wyjęciu z pudełka, więc prawdopodobnie będziesz musiał dostosować. Jeśli nie przeprowadzisz pełnej deserializacji, istnieje ryzyko, że plik XML zostanie zniekształcony w elemencie SalesPerson, a zatem dokument będzie nieprawidłowy.

Jeśli chcesz zaakceptować to ryzyko, prawdopodobnie zechcesz przeprowadzić podstawowe przetwarzanie tekstu, aby podzielić elementy SalesPerson na inny dokument, korzystając z prostych narzędzi do przetwarzania tekstu, a następnie przetworzyć kod XML.

Jest to dobry przykład, dlaczego XML nie zawsze jest poprawną odpowiedzią.

2

Myślę, że poprzedni komentujący ma rację w swoim komentarzu, że XML może nie być najlepszym wyborem sklepu z zapleczem tutaj.

Jeśli masz problemy ze skalą i nie korzystasz z innych elementów dostępnych w XML, takich jak transformacje, możesz lepiej wykorzystać bazę danych do swoich danych. Operacje, które robisz, wydają się bardziej pasować do tego modelu.

Wiem, że to naprawdę nie odpowiada na twoje pytanie, ale pomyślałem, że wyróżniłbym alternatywne rozwiązanie, którego możesz użyć. Dobra baza danych i odpowiedni program odwzorowujący OR, taki jak .netTiers, NHibernate, lub bardziej niedawno LINQ do SQL/Entity Framework, zapewne zapewniłoby ci powrót do działania z minimalnymi zmianami w pozostałej części bazy kodu.

+1

on może być tylko konsumentem na esb.so nie może zmienić jego części datastore.reading z plików XML jest uzasadniony process.with XmlReader niskiego poziomu możliwe jest do indeksowania plików/stream i seek/jump bezpośrednio do dowolnej pozycji w dokumencie. –

0

Możesz kontrolować, jakie części klasy samochody są rozszeregować poprzez wdrożenie IXmlSerializable interfejs na klasy samochodów, a następnie w ramach ReadXml (XmlReader) metoda będzie czytać i deserializowania elementy samochodu, ale po osiągnięciu element SalesPerson, który chcesz odczytać poddrzewo jako ciąg, a następnie skonstruuj Stream na treści tekstowej za pomocą StreamWriter.

Jeśli nie chcesz, aby XmlSerializer wypisał element SalesPerson, użyj atrybutu [XmlIgnore]. Nie jestem pewien, co chcesz zrobić, gdy klasyfikujesz klasę Cars do jej reprezentacji XML. Czy próbujesz zapobiec tylko deserializacji SalesPerson, a jednocześnie możesz serializować reprezentację XML SalesPerson reprezentowaną przez Stream?

Prawdopodobnie mógłbym podać przykład kodu, jeśli chcesz konkretnej implementacji.

0

Jeśli chcesz tylko sparsować element SalesPerson, ale zachować go jako ciąg, powinieneś użyć Xsl Transform zamiast "Deserialization". Jeśli, z drugiej strony, chcesz sparsować element SalesPerson i zapełnić obiekt w pamięci ze wszystkich innych elementów spoza SalesPerson, to Xsl Transform również może być drogą do zrobienia. Jeśli pliki są zbyt duże, możesz rozważyć ich rozdzielenie i użycie Xsl do połączenia różnych plików xml, tak aby dane wejściowe SalesPerson pojawiały się tylko wtedy, gdy tego potrzebujesz.

+0

Przypadkiem użycia jest to, że dane samochodu chcę jako obiekty, aby mój program mógł z nim współdziałać. Plik SalesPerson XML jest po prostu wysyłany przez przewód do innego systemu, więc nie muszę go nawet sprawdzać. Zasadniczo potrzebuję zebrać wszystkie dane, ale tylko dbać o to, co zawierają elementy Samochodu. – Mike

+0

Jeśli tak jest, to wszystko, co musisz zrobić, to nie dostarczać XmlElementAttributes do serializowania danych innych niż samochodowe. – devlord

+0

* deserializować, to znaczy – devlord

1

Spróbuj zdefiniować właściwość SalesPerson jako typ XmlElement. Działa to na wyjściu z usług internetowych ASMX, które korzystają z szeregowania XML. Myślę, że działałoby to również na sygnał wejściowy. Spodziewam się, że cały element <SalesPerson> zakończy się w XmlElement.

+0

Mogą również potrzebować elementu XmlAnyAttribute dla tego elementu. –

+0

Czy możesz powiedzieć dlaczego? –

+0

Mogę się pomylić, ponieważ wygląda na to, że XmlAny jest dla właściwości, która zwraca * tablicę * XmlElements, a nie tylko jedną. –

0

Sugerowałbym, aby ręcznie czytać z Xml, używając dowolnych lekkich metod, takich jak XmlReader, XPathDocument lub LINQ-to-XML.

Kiedy trzeba czytać tylko 3 właściwości, przypuszczam, można napisać kod, który ręcznie odczytać z tego węzła i mają pełną kontrolę tego, jak jest on wykonywany zamiast opierania się na serializacji/deserializacji

5

Przyjęty answer z user271807 jest doskonałym rozwiązaniem, ale uważam, że muszę również ustawić bazowy XML fragmentu w celu uniknięcia wyjątek z wewnętrznym wyjątku mówi coś takiego:

...xmlns=''> was not expected 

ten wyjątkiem był trown kiedy próbowałem deserializowania tylko wewnętrzny element uwierzytelniania tego dokumentu xML:

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

więc skończyło się tworząc tę ​​metodę rozszerzenia jako wielokrotnego użytku rozwiązanie - ostrzeżenie zawiera AM Emory wyciek, patrz poniżej:

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); 
      } 
     } 

Aktualizacja 20 marca 2017: jak poniżej komentarz zwraca uwagę, że jest problem wycieku pamięci podczas korzystania z jednego z konstruktorów XmlSerializer, więc skończyło się stosując roztwór buforowania jak pokazano poniżej:

/// <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); 
    } 
} 
+0

Pracowałem nad podobnym problemem i znalazłem twoje pytanie i [ten wpis na blogu] (https://blogs.msdn.microsoft.com/tess/2006/02/15/net-memory-leak-xmlserializing-your- way-to-a-memory-leak /) o wycieku pamięci podczas korzystania z konstruktora XmlSerializer (Type, XmlRootAttribute). Musisz sprawdzić swój kod. Myślę, że twoja metoda tworzy nowy tymczasowy montaż za każdym razem, gdy jest wywoływany. Prawdopodobnie będziesz musiał ręcznie buforować dla każdej kombinacji Type + innerStartTag. – plushpuffin

+0

Tak, dziękuję za przypomnienie. Zaktualizowałem swoją odpowiedź z poprawką. –