C #: render an object as XML

I am looking for a way to convert an object tree to XML. It would be interesting to write, but I'm sure someone already wrote this. Here is my wish list:

  • He does not have to care about designers
  • Ideally, it should handle circular links (don't fuss too much)
  • It should not require changes to objects - for example, no custom attributes
  • It should not care about the required types (e.g. XmlInclude) or require them
  • XML should be simple - it should be readable by team members.
  • If the property cannot be serialized, it should just suppress the error and continue
  • Can handle lists and dictionaries

I do not need to restore the object model, so the solution for writing is quite normal (maybe expected).

I think the discounts are:

  • XmlSerializer - needs constructors without parameters, without circular reference support.
  • DataContractSerializer - needs attributes (select)
+6
c # xml serialization
source share
3 answers

It seems that it would be easy to write using reflection: given the instance of the object, create an XML element with its class name, and then repeat all its properties.

For each property, create an element with its name:

  • if it is a value type, set its text to the XML schema text of its value;
  • if it implements IEnumerable , IEnumerable over it and creates an element for each element;
  • if it is any other reference type, set the content of the element to the XML representation of the property.

Track circular / multiple links using a HashSet containing the hash codes of each object that you serialize; if you find the hash code of an object in a HashSet, you have already serialized it. (I don't know what you want to put in XML if that happens.)

But no, I don't have code that does this.

+4
source share

Robert Rossney’s message made me think it was probably less than I thought. So here is a very rude attempt. It processes the following:

  • If it cannot read the property, it prints the exception as a value
  • Loops and multiple occurrences. It associates an identifier with each element; if an element appears twice, it simply indicates the identifier ref. The Ref identifier is unique to the object graph (I should probably use a GUID, but this is consistent with my goals).
  • It has no problems with derived types.
  • It does not require any attributes or specific constructors or other nonsense.
  • It can handle read-only properties.

Here is an example of output (in my test objects, the product "Currency" on the order throws an exception).

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

Here is an example model of the object and its use:

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

Here's the implementation:

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

I would still be interested to know if there are any more tried and tested solutions.

+6
source share

I doubt that you will find anything that works particularly well in all classes. As you pointed out, XmlSerializer is Microsoft's best effort at the overall end of things so far.

On the other hand, visualizers that are unique to a particular class. I do not think there is a lot of happy environment.

+1
source share

All Articles