Making sense of XSD type names, in C#

April 17, 2009 at 11:00 PM | categories: Uncategorized | View Comments

In response to a conversation with @phil_nash earlier this week:

I have some data from an XML document, along with a string indicating the type of that data, as one of the XSD type names. The .NET XML serializer knows all about these XSD types, since it'll emit xsi:type attributes on elements that correspond to .NET properties of type object.

However, there doesn't seem to be an easy way of parsing these XSD type names from your own code. A little digging led me to the XmlSerializationReader.ReadTypedPrimitive method, which is (naturally) declared as private.

At this point I have three options:

  1. Call ReadTypedPrimitive directly, using Reflection. Evil, since I'd rather not write code that accesses framework methods declared as private.
  2. Write my own equivalent of the ReadTypedPrimitive method. This appears to involve a giant switch statement.
  3. Come up with some way of persuading the framework to call ReadTypedPrimitive on my behalf.

The XmlSerializationReader class is in fact used as a base class for the code generated for you when you call the XmlSerializer constructor. I realised that, in order to arrange for the framework to make this method call, I just need a small serializable class that results in the right auto-generated code.

In summary, I went with option 3: construct an XML document in memory, containing the string I originally wanted converted, and the XSD type name supplied to me. Running that through the XmlSerializer causes ReadTypedPrimitive -- with its large switch statement -- to get called, and I end up with a .NET object whose type corresponds to the XSD type of my choice.

using System;
using System.Diagnostics;
using System.Xml;
using System.Xml.Serialization;

public class XmlValueWrapper
{
    private object value;

    public object Value
    {
        get { return value; }
        set { this.value = value; }
    }
}

public static class XsdConvert
{
    private static XmlSerializer serializer = new XmlSerializer(typeof(XmlValueWrapper));

    public static object ConvertFrom(string value, string xsdType)
    {
        XmlDocument doc = new XmlDocument();
        XmlElement rootElement = (XmlElement) doc.AppendChild(doc.CreateElement("XmlValueWrapper"));
        rootElement.SetAttribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema");

        XmlElement valueElement = (XmlElement) rootElement.AppendChild(doc.CreateElement("Value"));
        valueElement.SetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance", xsdType);
        valueElement.AppendChild(doc.CreateTextNode(value));

        using (XmlNodeReader reader = new XmlNodeReader(doc))
        {
            XmlValueWrapper wrapper = (XmlValueWrapper) serializer.Deserialize(reader);
            return wrapper.Value;
        }
    }
}

public static class Program
{
    public static void Main()
    {
        Debug.Assert(Equals(42, XsdConvert.ConvertFrom("42", "xs:int")));
        Debug.Assert(Equals(42.0, XsdConvert.ConvertFrom("42", "xs:double")));
        Debug.Assert(Equals(42m, XsdConvert.ConvertFrom("42", "xs:decimal")));
        Debug.Assert(Equals("42", XsdConvert.ConvertFrom("42", "xs:string")));
        Debug.Assert(Equals(true, XsdConvert.ConvertFrom("true", "xs:boolean")));
        Debug.Assert(Equals(new DateTime(2009, 4, 17), XsdConvert.ConvertFrom("2009-04-17", "xs:date")));
    }
}
blog comments powered by Disqus