2009-02-23 13 views
6

Sto cercando un modo per convertire un albero di oggetti in XML. Sarebbe divertente scrivere, ma sono sicuro che qualcuno l'ha già scritto. Ecco la mia lista dei desideri:C#: Render object as XML

  • Non dovrebbe preoccuparsi costruttori
  • Si dovrebbe idealmente gestire i riferimenti circolari (non troppo coccolato come)
  • Non dovrebbe richiedere modifiche agli oggetti - ad esempio, attributi personalizzati
  • non dovrebbe preoccuparsi o richiedere tipi noti (ad esempio, XmlInclude)
  • l'XML dovrebbe essere morto semplice - ha bisogno di essere leggibile dai membri del team operativo
  • Se una proprietà non può essere seriali Zed, si deve solo sopprimere l'errore e continuare
  • in grado di gestire liste e dizionari

non ho bisogno di ricostruire il modello a oggetti, quindi una soluzione di sola scrittura va bene (probabilmente si aspettava).

Penso che gli sconti:

  • XmlSerializer - ha bisogno di costruttori senza parametri, nessun supporto riferimento circolare
  • DataContractSerializer - ha bisogno di attributi (opt-in)
+0

Buona domanda! : P – Cerebrus

+0

Interessante domanda. Ho paura che i requisiti "Non dovrebbe interessare i costruttori" potrebbero non essere soddisfatti. Come può il serializzatore sapere quale costruttore usare se il mio tipo ha 10 costruttori diversi? –

+0

@siz - Devo chiarire. Non importa perché è solo serializzazione (oggetto in XML) e mai de-serializzazione (da XML a oggetto). Questo è ciò che intendevo con una soluzione "sola scrittura". –

risposta

4

Sembra che sia semplice scrivere utilizzando reflection: data un'istanza di oggetto, creare un elemento XML con il relativo nome di classe e quindi scorrere tutte le sue proprietà.

Per ogni proprietà creano un elemento con il suo nome:

  • se si tratta di un tipo di valore, impostare il suo testo al testo XML Schema del suo valore;
  • se implementa IEnumerable, itera attraverso di esso e crea un elemento per ogni elemento;
  • se si tratta di un altro tipo di riferimento, impostare il contenuto dell'elemento nella rappresentazione XML della proprietà.

Traccia circolare/più riferimenti con un hashset contenente i codici hash di ciascun oggetto che hai serializzato; se trovi il codice hash di un oggetto in HashSet, lo hai già serializzato. (Non so cosa vuoi mettere nell'XML se questo accade.)

Ma no, non ho alcun codice che faccia questo in giro.

1

dubito che si sta andando a trovare tutto ciò che funziona particolarmente bene in tutte le classi. Come hai sottolineato, XmlSerializer è il miglior sforzo di Microsoft per la fine generica finora.

Sull'altra estremità sono visualizers, che sono univoci per una classe specifica. Non penso che ci sia ancora molto di un mezzo felice.

6

Il post di Robert Rossney mi ha fatto pensare che probabilmente è meno lavoro di quanto pensassi. Quindi, ecco un tentativo molto approssimativo.Gestisce il seguente:

  • Se non è in grado di leggere una proprietà, viene stampato l'eccezione come il valore
  • riferimenti ciclici e più occorrenze. Associa un ID a ciascun elemento; se un elemento appare due volte, punta semplicemente l'ID di riferimento. L'ID Ref è univoco per il grafico dell'oggetto (probabilmente dovrei usare un GUID, ma questo è adatto ai miei scopi).
  • Non ha problemi con tipi derivati ​​
  • non richiede attributi o costruttori specifici o altre sciocchezze
  • E 'in grado di gestire proprietà di sola lettura

Ecco un esempio di output (nei miei oggetti di prova , il prodotto "Valuta" sull'Ordine genera un'eccezione).

<Customer Ref="1"> 
    <FirstName>Paul</FirstName> 
    <LastName>Stovell</LastName> 
    <FullName>Paul Stovell</FullName> 
    <Orders> 
    <Order Ref="2"> 
     <SKU>Apples</SKU> 
     <Price>27.30</Price> 
     <Currency>Something bad happened</Currency> 
     <Customer Ref="1" /> 
    </Order> 
    <Order Ref="3"> 
     <SKU>Pears</SKU> 
     <Price>17.85</Price> 
     <Currency>Something bad happened</Currency> 
     <Customer Ref="1" /> 
    </Order> 
    <Order Ref="2" /> 
    </Orders> 
</Customer> 

Ecco il modello a oggetti del campione e il loro utilizzo:

static void Main(string[] args) 
{ 
    var customer = new Customer(); 
    customer.FirstName = "Paul"; 
    customer.LastName = "Stovell"; 
    customer.Orders.Add(new Order(customer) { Price = 27.30M, SKU = "Apples"}); 
    customer.Orders.Add(new Order(customer) { Price = 17.85M, SKU = "Pears"}); 
    customer.Orders.Add(customer.Orders[0]); 

    var output = new StringWriter(); 
    var writer = new XmlTextWriter(output); 
    writer.Formatting = Formatting.Indented; 
    WriteComplexObject("Customer", customer, writer); 
    Console.WriteLine(output.ToString()); 
    Console.ReadKey(); 
} 

class Customer 
{ 
    private readonly List<Order> _orders = new List<Order>(); 

    public Customer() 
    { 
    } 

    public string FirstName { get; set; } 
    public string LastName { get; set; } 

    public string FullName 
    { 
     // Read-only property test 
     get { return FirstName + " " + LastName; } 
    } 

    public List<Order> Orders 
    { 
     // Collections test 
     get { return _orders; } 
    } 
} 

class Order 
{ 
    private readonly Customer _customer; 

    public Order(Customer customer) 
    { 
     _customer = customer; 
    } 

    public string SKU { get; set; } 
    public decimal Price { get; set; } 
    public string Currency 
    { 
     // A proprty that, for some reason, can't be read 
     get 
     { 
      throw new Exception("Something bad happened"); 
     } 
    } 

    public Customer Customer 
    { 
     get { return _customer; } 
    } 
} 

Ecco l'attuazione:

public static void WriteObject(string name, object target, XmlWriter writer) 
{ 
    WriteObject(name, target, writer, new List<object>(), 0, 10, -1); 
} 

private static void WriteObject(string name, object target, XmlWriter writer, List<object> recurringObjects, int depth, int maxDepth, int maxListLength) 
{ 
    var formatted = TryToFormatPropertyValueAsString(target); 
    if (formatted != null) 
    { 
     WriteSimpleProperty(name, formatted, writer); 
    } 
    else if (target is IEnumerable) 
    { 
     WriteCollectionProperty(name, (IEnumerable)target, writer, depth, maxDepth, recurringObjects, maxListLength); 
    } 
    else 
    { 
     WriteComplexObject(name, target, writer, recurringObjects, depth, maxDepth, maxListLength); 
    } 
} 

private static void WriteComplexObject(string name, object target, XmlWriter writer, List<object> recurringObjects, int depth, int maxDepth, int maxListLength) 
{ 
    if (target == null || depth >= maxDepth) return; 
    if (recurringObjects.Contains(target)) 
    { 
     writer.WriteStartElement(name); 
     writer.WriteAttributeString("Ref", (recurringObjects.IndexOf(target) + 1).ToString()); 
     writer.WriteEndElement(); 
     return; 
    } 
    recurringObjects.Add(target); 

    writer.WriteStartElement(name); 
    writer.WriteAttributeString("Ref", (recurringObjects.IndexOf(target) + 1).ToString()); 
    foreach (var property in target.GetType().GetProperties()) 
    { 
     var propertyValue = ReadPropertyValue(target, property); 
     WriteObject(property.Name, propertyValue, writer, recurringObjects, depth + 1, maxDepth, maxListLength); 
    } 
    writer.WriteEndElement(); 
} 

private static object ReadPropertyValue(object target, PropertyInfo property) 
{ 
    try { return property.GetValue(target, null); } 
    catch (Exception ex) { return ReadExceptionMessage(ex); } 
} 

private static string ReadExceptionMessage(Exception ex) 
{ 
    if (ex is TargetInvocationException && ex.InnerException != null) 
     return ReadExceptionMessage(ex.InnerException); 
    return ex.Message; 
} 

private static string TryToFormatPropertyValueAsString(object propertyValue) 
{ 
    var formattedPropertyValue = null as string; 
    if (propertyValue == null) 
    { 
     formattedPropertyValue = string.Empty; 
    } 
    else if (propertyValue is string || propertyValue is IFormattable || propertyValue.GetType().IsPrimitive) 
    { 
     formattedPropertyValue = propertyValue.ToString(); 
    } 
    return formattedPropertyValue; 
} 

private static void WriteSimpleProperty(string name, string formattedPropertyValue, XmlWriter writer) 
{ 
    writer.WriteStartElement(name); 
    writer.WriteValue(formattedPropertyValue); 
    writer.WriteEndElement(); 
} 

private static void WriteCollectionProperty(string name, IEnumerable collection, XmlWriter writer, int depth, int maxDepth, List<object> recurringObjects, int maxListLength) 
{ 
    writer.WriteStartElement(name); 
    var enumerator = null as IEnumerator; 
    try 
    { 
     enumerator = collection.GetEnumerator(); 
     for (var i = 0; enumerator.MoveNext() && (i < maxListLength || maxListLength == -1); i++) 
     { 
      if (enumerator.Current == null) continue; 
      WriteComplexObject(enumerator.Current.GetType().Name, enumerator.Current, writer, recurringObjects, depth + 1, maxDepth, maxListLength); 
     } 
    } 
    catch (Exception ex) 
    { 
     writer.WriteElementString(ex.GetType().Name, ReadExceptionMessage(ex)); 
    } 
    finally 
    { 
     var disposable = enumerator as IDisposable; 
     if (disposable != null) 
     { 
      disposable.Dispose(); 
     } 
     writer.WriteEndElement(); 
    } 
} 

sarei ancora interessato a sapere se ci sono soluzioni più collaudate.