Showing posts with label Serialization. Show all posts
Showing posts with label Serialization. Show all posts

Day 2: Serialization of Complex Types and XmlAttributes

This isn't really a rant about Windows Mobile per se, but it's an issue I seem to run into on a consistent basis: you can't deserialize an XmlAttribute into a struct. Consider the following code:

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;

namespace SmartDeviceProject2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            XmlSerializer ser = new XmlSerializer(typeof(Foo));

            string xml = "<Foo Moo=\"2,3\"/>";
            MemoryStream mem = new MemoryStream();
            StreamWriter writer = new StreamWriter(mem);
            writer.Write(xml);
            writer.Flush();
            mem.Seek(0, SeekOrigin.Begin);
            Foo f = (Foo)ser.Deserialize(mem);
        }
    }

    public struct Bar : IFormattable
    {
        public int X;
        public int Y;
        public static Bar Parse(string s)
        {
            string[] strings = s.Split(',');
            Bar ret = new Bar();
            ret.X = int.Parse(strings[0]);
            ret.Y = int.Parse(strings[1]);
            return ret;
        }
        public override string ToString()
        {
            return string.Format("{0},{1}", X, Y);
        }

        #region IFormattable Members

        public string ToString(string format, IFormatProvider formatProvider)
        {
            return ToString();
        }

        #endregion
    }

    public struct Foo
    {
        [XmlAttribute]
        public Bar Moo;
    }
}

Attempting to run this code on the desktop would fail on the line that instantiates the XmlSerializer: "Cannot serialize member 'Moo' of type SmartDeviceProject2.Foo. XmlAttribute/XmlText cannot be used to encode complex types.".

However, on .NET CF 2.0, it fails on deserialization with the cryptic message: "The type SmartDeviceProject2.Bar was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

But my gripe is the following: I have a struct, it is IFormattable, and it can be Parsed (like int.Parse, DateTime.Parse, et al). Why is that not sufficient enough for serialization to and from an XmlAttribute string?

Anyways, here is the workaround:

  1. For all types that you want to be deserialized from attributes, explicitly give them the [XmlElement] attribute. Yes, you read that right.
  2. Create an XmlSerializer. Attach a new method to the UnknownAttribute event.
  3. Whenever this event is fired, use a combination of the attribute name and reflection to find the PropertyInfo/FieldInfo for what XmlSerializer can't deserialize. Then find that object type's static Parse method, and call it on the attribute string. Then use that PropertyInfo/FieldInfo to set the property.

The new code would look something like this (you will need to add your own code to handle if it is a Property, since my sample is only looking for fields):

private void button1_Click(object sender, EventArgs e)
{
    XmlSerializer ser = new XmlSerializer(typeof(Foo));

    string xml = "<Foo Moo=\"2,3\"/>";
    MemoryStream mem = new MemoryStream();
    StreamWriter writer = new StreamWriter(mem);
    writer.Write(xml);
    writer.Flush();
    mem.Seek(0, SeekOrigin.Begin);
    ser.UnknownAttribute += new XmlAttributeEventHandler(ser_UnknownAttribute);
    Foo f = (Foo)ser.Deserialize(mem);
}

void ser_UnknownAttribute(object sender, XmlAttributeEventArgs e)
{
    FieldInfo field = e.ObjectBeingDeserialized.GetType().GetField(e.Attr.Name);
    MethodInfo parse = field.FieldType.GetMethod("Parse");
    field.SetValue(e.ObjectBeingDeserialized, parse.Invoke(null, new object[] { e.Attr.Value }));
}
public struct Foo
{
    [XmlElement]
    public Bar Moo;
}

Unfortunately I have not figured out a similar workaround for serialization...