2009-02-23 15 views
6

Estoy buscando una forma de convertir un árbol de objetos a XML. Sería divertido escribir, pero estoy seguro de que alguien ya lo ha escrito. Aquí está mi lista de deseos:C#: Renderizar objeto como XML

  • No debe preocuparse por los constructores
  • Idealmente debería manejar referencias circulares (no se preocupan mucho cómo)
  • No debe requerir cambios en los objetos - por ejemplo, no hay atributos personalizados
  • no debe preocuparse o prescriban los tipos conocidos (por ejemplo, XmlInclude)
  • el XML debe ser simple muertos - que tiene que ser legible por miembros del equipo de operaciones
  • Si una propiedad no puede ser seriali Zed, se debe simplemente a suprimir el error y continuar
  • Puede manejar listas y diccionarios

que no necesito para reconstruir el modelo de objetos, por lo que una solución de sólo escritura está muy bien (probablemente esperaba).

creo que los descuentos:

  • XmlSerializer - necesita constructores sin parámetros, no hay soporte referencia circular
  • DataContractSerializer - necesita atributos (opt in)
+0

¡Buena pregunta! : P – Cerebrus

+0

pregunta interesante. Me temo que los requisitos de "No debería importar sobre constructores" podrían no ser posibles. ¿Cómo puede el serializador saber qué constructor usar si mi tipo tiene 10 constructores diferentes? –

+0

@siz - Debo aclarar. No le importa porque solo está serializando (objeto a XML) y nunca deserializando (XML a objeto). Eso es lo que quise decir con una solución de "solo escritura". –

Respuesta

4

Parece que sería sencillo escribir usando la reflexión: dada una instancia de objeto, cree un elemento XML con su nombre de clase, y luego itere a través de todas sus propiedades.

Para cada propiedad crear un elemento con su nombre:

  • si se trata de un tipo de valor, establecer su texto para el texto de esquema XML de su valor;
  • si implementa IEnumerable, repítalo y cree un elemento para cada elemento;
  • si es cualquier otro tipo de referencia, establezca el contenido del elemento en la representación XML de la propiedad.

Rastrea referencias circulares/múltiples con un HashSet que contiene los códigos hash de cada objeto que has serializado; si encuentra el código hash de un objeto en el HashSet, ya lo habrá serializado. (No sé lo que quiere poner en el XML si esto sucede.)

Pero no, no tengo ningún código que lo haga por ahí.

1

dudo que vas a encontrar cualquier cosa que funcione particularmente bien en todas las clases. Como señaló, XmlSerializer es el mejor esfuerzo de Microsoft en el extremo genérico de las cosas hasta el momento.

En el otro extremo están visualizers, que son exclusivos de una clase específica. No creo que haya mucho de un medio feliz todavía.

6

La publicación de Robert Rossney me hizo pensar que probablemente sea menos trabajo de lo que pensaba. Así que aquí hay un intento muy duro.Se ocupa de lo siguiente:

  • Si no es capaz de leer una propiedad, se imprime la excepción, ya que el valor
  • Referencias cíclicas y múltiples ocurrencias. Asocia una identificación con cada elemento; si un elemento aparece dos veces, solo señala la ID de la referencia. El Id. De referencia es exclusivo del gráfico de objetos (probablemente debería usar un GUID, pero esto se ajusta a mis propósitos).
  • No tiene problemas con tipos derivados
  • No requiere de atributos o constructores específicos u otro sentido
  • Puede manejar propiedades de sólo lectura-

He aquí un ejemplo de la salida (en mis objetos de prueba , el producto "Moneda" en la Orden arroja una excepción).

<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> 

Aquí está el modelo de objetos de la muestra y su uso:

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

Aquí está la implementación:

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

todavía estaría interesado en saber si hay más soluciones probadas.

Cuestiones relacionadas