Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: Render object as XML

I am looking for a way to convert an object tree to XML. It would be fun to write, but I am sure someone has already written it. Here is my wish list:

  • It should not care about constructors
  • It should ideally handle circular references (not too fussed how)
  • It should not require changes to the objects - e.g., no custom attributes
  • It should not care about or require known types (e.g., XmlInclude)
  • The XML should be dead simple - it needs to be human readable by members of the operations team
  • If a property can't be serialized, it should just suppress the error and continue
  • Can handle lists and dictionaries

I don't need to reconstruct the object model, so a write-only solution is fine (probably expected).

I think that discounts:

  • XmlSerializer - needs parameterless constructors, no circular reference support
  • DataContractSerializer - needs attributes (opt in)
like image 364
Paul Stovell Avatar asked Feb 23 '09 18:02

Paul Stovell


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is C language basics?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.


1 Answers

Robert Rossney's post made me think it's probably less work than I thought. So here's a very rough attempt. It handles the following:

  • If it is unable to read a property, it prints the exception as the value
  • Cyclic references and multiple occurrences. It associates an ID with each element; if an element appears twice, it just points the ref ID. The Ref ID is unique to the object graph (I should probably use a GUID, but this suits my purposes).
  • It has no problems with derived types
  • It requires no attributes or specific constructors or other nonsense
  • It can handle read-only properties

Here's an example of the output (in my test objects, the "Currency" product 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's the sample object model and usage:

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 more tried and tested solutions.

like image 179
Paul Stovell Avatar answered Oct 10 '22 08:10

Paul Stovell