0

I'm posting this on a FIFTY-FITY basis.

50% likely I am missing something obvious and others will tell me why I am a doofus - in which case, can this be done more simply? Tell me I'm wrong if you are willing -- but please don't bite too hard!

50% my solution is actually useful and others might appreciate it.

Either way, I hope to learn something.

I am using XML serialization for a very heavy-duty project where I have a production-line of analytics. There are many steps in the production line so I have written some very abstract base machinery to underlie them all. It has been a lot of work! 60k lines of code. 1,000 Types.

What I want to do now is serialize any part of the production line to XML without having to write custom serialization code for each component in the production line. I want a single approach, held in a base class. Nothing unusual about that so far.

But the snitch is that C# (well, .NET)

  1. Cannot serialize Dictionaries
  2. Cannot serialize Interfaces
  3. Offers limited debugging, logging and inspectability in XML serialization.

So I wrote the following function in my base class that everything inherits.

    public void WriteXml(XmlWriter writer)
    {
        Console.WriteLine("############### WRITING XML FOR " + GetType() + " " + this.Name);

        foreach (FieldInfo fieldInfo in this.GetFieldInfos(this))
        {
            try
            {
                string fieldName  = fieldInfo.Name;
                var fieldValue = fieldInfo.GetValue(this);
                if (!IsBackFieldName(fieldName))
                {
                    Console.WriteLine("Serializing\t" + fieldInfo.FieldType + "\t\t" + fieldName + "\t" + fieldValue);
                    if (fieldInfo.FieldType.IsDictionary())
                    {
                        // TODO intercept any Dictionary type and convert to SerializableDictionary
                        ;
                    }
                    writer.WriteStartElement(fieldName);
                    if (fieldInfo.FieldType.IsXmlSerializable())
                    {
                        XmlSerializer xmlSerializer;
                        if (fieldInfo.FieldType.IsInterface)
                        {
                            // look through the interface to the underlying type, which will have a parameterless constructor for serialization
                            IData castedValue      = fieldValue as IData;
                            Type  lookThroughClass = castedValue.SerializationType;
                            xmlSerializer = new XmlSerializer(lookThroughClass);
                        }
                        else
                            xmlSerializer = new XmlSerializer(fieldInfo.FieldType);
                        // serialization here can be built-in or overriden if IXmlSerializable is implemented
                        xmlSerializer.Serialize(writer, fieldValue);
                    }
                    else
                    {
                        writer.WriteComment("Not serializable " + fieldInfo.FieldType);
                    }
                    writer.WriteEndElement();
                }
                else
                {
                    // skip backing field
                    Console.WriteLine("SKIPPING\t" + fieldInfo.FieldType + "\t\t" + fieldName + "\t" + fieldValue);
                    ;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error writing XML: " + e.Message);
            }
        }

        foreach (PropertyInfo propertyInfo in GetPropertyInfos(this))
        {
            try
            {
                string propertyName = propertyInfo.Name;
                var propertyValue = propertyInfo.GetValue(this);
                if (!IsBackFieldName(propertyName))
                {
                    Console.WriteLine("Serializing\t" + propertyInfo.PropertyType + "\t\t" + propertyName + "\t" + propertyValue);
                    // TODO intercept any Dictionary type and convert to SerializableDictionary
                    if (propertyInfo.PropertyType.IsDictionary())
                    {
                        // TODO intercept any Dictionary type and convert to SerializableDictionary
                        ;
                    }
                    writer.WriteStartElement(propertyName);
                    if (propertyInfo.PropertyType.IsXmlSerializable())
                    {
                        XmlSerializer xmlSerializer;
                        if (propertyInfo.PropertyType.IsInterface)
                        {
                            // look through the interface to the underlying type, which will have a parameterless constructor for serialization
                            IData castedValue      = propertyValue as IData;
                            Type  lookThroughClass = castedValue.SerializationType;
                            xmlSerializer = new XmlSerializer(lookThroughClass);
                        }
                        else
                            xmlSerializer = new XmlSerializer(propertyInfo.PropertyType);

                        // serialization here can be built-in or overriden if IXmlSerializable is implemented
                        xmlSerializer.Serialize(writer, propertyValue);
                    }
                    else
                    {
                        writer.WriteComment("Not serializable " + propertyInfo.PropertyType);
                    }
                    writer.WriteEndElement();
                }
                else
                {
                    // skip backing field
                    Console.WriteLine("SKIPPING\t" + propertyInfo.PropertyType + "\t\t" + propertyName + "\t" + propertyValue);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error writing XML: " + e.Message);
            }

        }

        return;
    }

The supporting infrastructure is:

public interface IData
    : IXmlSerializable
{
    #region  Members

    string   Name     { get; }

    #endregion

    #region Methods

    /// <summary>
    /// Allows us to pass the underlying type back out through an interface.
    /// We have to know the actual type for serialization, because interfaces cannot be instantiated.
    /// </summary>
    Type SerializationType { get; }

    #endregion

}

IData is implemented by all members of the production line which also inherit the XML write and read methods in the base class. The key (possibly sneaky) part is that each child class has to implement SerializationType locally:

    public Type SerializationType => GetType();

This allows the locally-scoped Type to be made visible in the interface.

These are helper functions in the main method:

    public PropertyInfo[] GetPropertyInfos(object theObject)
    {
        // https://stackoverflow.com/questions/6536163/how-to-list-all-variables-of-class
        BindingFlags bindingFlags = BindingFlags.Instance  |
                                    BindingFlags.NonPublic |
                                    BindingFlags.Public;
        // Type           theType       = this.GetType();
        Type           theType       = theObject.GetType();
        PropertyInfo[] propertyInfos = theType.GetProperties(bindingFlags);
        return propertyInfos;
    }

    public FieldInfo[] GetFieldInfos(object theObject)
    {
        // https://stackoverflow.com/questions/6536163/how-to-list-all-variables-of-class
        BindingFlags bindingFlags = BindingFlags.Instance  |
                                    BindingFlags.NonPublic |
                                    BindingFlags.Public;
        // Type        theType    = this.GetType();
        Type        theType    = theObject.GetType();
        FieldInfo[] fieldInfos = theType.GetFields(bindingFlags);
        return fieldInfos;
    }

And in a static class of extensions:

    public static bool IsXmlSerializable(this Type testType)
    {
        if (testType.IsSerializable)
            return true;
        // just for good measure
        var interfaces = testType.GetInterfaces().ToList();
        if (interfaces.Contains(typeof(IXmlSerializable)))
            return true;
        return false;
    }

So

  1. Is this approach sound?
  2. Can it be improved or abbreviated?
  3. Has someone else already published a better solution?
Bit Racketeer
  • 493
  • 1
  • 3
  • 13
  • You could serialize as JSON then convert to XML using [tag:json.net]. This should work since Json.NET doesn't have any restrictions serializing interfaces or dictionaries. See [How to convert JSON to XML or XML to JSON?](https://stackoverflow.com/q/814001/3744182). – dbc Jun 09 '18 at 19:57
  • @dbc thanks for comment ... I've never used JSON. Can you explain a little about why this approach is better? – Bit Racketeer Jun 09 '18 at 23:00
  • Unlike `XmlSerializer` which is designed to serialize types that can be round-tripped, Json.NET is perfectly happy to serialize types that it might not know how to deserialize. Thus it can serialize interfaces, because it will serialize the actual, concrete type(s) encountered. And it can also [serialize dictionaries](https://www.newtonsoft.com/json/help/html/SerializeDictionary.htm). For more see https://www.newtonsoft.com/json/help/html/ConvertingJSONandXML.htm – dbc Jun 09 '18 at 23:04

0 Answers0