Stack Overflow profile for md5sum

Monday, October 26, 2009

Serialize Objects Manually

Edit (27 Oct, 2009 14:31 GMT-600): I realized earlier that this solution doesn't handle arrays and generics. However, I don't really need that functionality, and therefore won't be updating this post with the information. If I need it later, I'll implement it.

I recently found an instance in which I needed to pass a System.Windows.Shapes (part of the PresentationFramework) object through WCF and store it in a database. Shortly after starting with this problem, I realized that not only were all the shapes not marked Serializable, but the classes were also sealed, preventing inheritance. So now I had to dig deeper into my options to get these guys from my client to my server.

My first few attempts at this were hardly heartfelt, and didn't succeed at all, as I was just trying to get it done as fast as possible (the worst thing a programmer can do... good for me). Finally, I realized I wasn't making any headway on this problem, and I started reading other people's experiences, as well as asking if anyone had any tips on several forums. The best advice I could find in all of this was just to use Reflection and serialize the object myself. So, that's just what I did. And, since I'm so fond of sharing the coolest things I find to do, I'm sharing how to do this, both for my own future reference and for yours. Enjoy!


/// <summary>
/// Serializes and Deserializes objects not marked with the Serializable attribute.
/// NOTE: Named Serialize to prevent interference with the
/// System.Runtime.Serialization namespace.
/// </summary>
public static class Serialize
{
    /// <summary>
    /// Used for thread safety on the SerializeObject(object obj) method.
    /// </summary>
    private static object serializeSync = new object();

    /// <summary>
    /// Used for thread safety on the DeserializeObject(string s) method.
    /// </summary>
    private static object deserializeSync = new object();

    /// <summary>
    /// Serializes an object into an xml formatted string.
    /// </summary>
    /// <param name="obj">The object to serialize.</param>
    /// <returns>Xml formatted string representing the "obj" parameter</returns>
    public static string SerializeObject(object obj)
    {
        // Make this thread safe.
        lock (serializeSync)
        {
        // Make a new StringBuilder seeded with a default xml root
        StringBuilder sb = new StringBuilder("<object type=\"" +
            obj.GetType().Namespace + "." + obj.GetType().Name + ", " +
            obj.GetType().Assembly.FullName + "\">");

        // Get the PropertyInfos from the object.
        PropertyInfo[] pInfos = obj.GetType().GetProperties();

        // Make sure there are properties.
        if (pInfos.Length > 0)
        {
            // Loop through each property.
            foreach (PropertyInfo pi in pInfos)
            {
                try
                {
                    // Make sure you can write to the property, and
                    // that it isn't null or empty.
                    if (pi.CanWrite && pi.GetValue(obj, null) != null &&
                        pi.GetValue(obj, null).ToString() != "")
                    {
                        // Get the property value, make sure it's
                        // serialized as well.
                        string tmp = SerializeObject(pi.GetValue(obj, null));

                        // Make sure that the property was properly serialized.
                        if (tmp != "")
                        // Add it to the xml string.
                        sb.Append("<property name=\"" + pi.Name + "\">" +
                            tmp + "</property>");
                    }
                }
                catch
                {
                    // TODO: Implement error logging.
                }
            }
        }
        // If there are no properties, treat it as a low level type.
        else
        {
            // See if it was an enumeration member.
            if (obj.GetType().IsEnum)
                // Return the int value of the enumeration member.
                return ((int)obj).ToString();
            else
                // Return the string value of the object.
                return obj.ToString();
        }

        // Make sure we're not returning an empty object.
        if (sb.Length == ("<object type=\"" + obj.GetType().Namespace + "." +
            obj.GetType().Name + ", " + obj.GetType().Assembly.FullName +
            "\">").Length)
        {
            // Return an empty string, rather than the empty object tag.
            return "";
        }

        // End the root element.
        sb.Append("</object>");

        // Return the xml.
        return sb.ToString();
    }
}


    /// <summary>
    /// Deserializes a string created using the SerializeObject(object obj) method.
    /// </summary>
    /// <param name="s">The string returned by the SerializeObject(object obj)
    /// method.</param>
    /// <returns>The object which was formerly serialized.</returns>
    public static object DeserializeObject(string s)
    {
        // Make this thread safe.
        lock (deserializeSync)
        {
            // Make a new XmlDocument.
            XmlDocument xDoc = new XmlDocument();

            try
            {
                // Try to load the xml formatted string.
                xDoc.LoadXml(s);
            }
            catch
            {
                // TODO: Implement error logging.
                return null;
            }

            // Make sure something got loaded.
            if (xDoc.OuterXml != null && xDoc.OuterXml != "")
            {
                // Get the type of object being deserialized.
                Type t = Type.GetType(xDoc.ChildNodes[0].Attributes["type"].Value);
                // Make a new instance of the object.
                object o = Activator.CreateInstance(t);

                // Get a list of the property nodes.
                XmlNodeList xNodeList = xDoc.ChildNodes[0].ChildNodes;

                // Make sure there are child nodes.
                if (xNodeList.Count > 0)
                {
                    // Loop through all the properties.
                    foreach (XmlNode xNode in xDoc.ChildNodes[0].ChildNodes)
                    {
                        // Make sure the node meets the base criteria.
                        if (xNode.Name == "property" &&
                            xNode.Attributes.Count == 1)
                        {
                            // Loop through all the properties of the current
                            // instance of the serializesd object.
                            foreach (PropertyInfo pi in o.GetType().GetProperties())
                            {
                                // Make sure the property name and the node's property name match, and
                                // you can write to the property.
                                if (xNode.Attributes["name"].Value == pi.Name && pi.CanWrite)
                                {
                                    try
                                    {
                                        // See if the property is an enumeration.
                                        if (pi.PropertyType.IsEnum)
                                        {
                                            // Set the value of the property in our new
                                            // object to the int value of the serialized object.
                                            pi.SetValue(o, Convert.ToInt32(DeserializeObject(xNode.InnerXml)), null);
                                        }
                                        else
                                        {
                                            // Set the value of the property in our new object to the properly
                                            // typed value of the serialized object.
                                            pi.SetValue(o, Convert.ChangeType(
                                                DeserializeObject(xNode.InnerXml), pi.PropertyType), null);
                                            }
                                        }
                                        catch
                                        {
                                            // Set the value to whatever the deserialization
                                            // method returns (last ditch effort).
                                            pi.SetValue(o, DeserializeObject(xNode.InnerXml), null);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    // If there were no serialized properties, treat it as a low level type.
                    else
                    {
                        // Make sure there was a value stored.
                        if (xDoc.InnerText != "")
                        {
                            // Properly type the object and set it's value.
                            o = Convert.ChangeType(xDoc.InnerText, o.GetType());
                        }
                    }

                    // Return our new object.
                    return o;
                }
                // Our document loaded successfully, but was empty.
                else
                {
                    // Return an empty object.
                    return null;
                }
            }
        }
    }
}

No comments: