What Was I Thinking?

Follies & Foils of .NET Development
posts - 95 , comments - 352 , trackbacks - 0

XMLSerialization - Solving the "Type Not Statically Known" exception

.NET's XMLSerializer can be pretty stupid.  It refuses to serialize an object if its properties are of a derived type.  Consider the following example:

[Serializable]
public classPerson
{
    public stringFirstName { get; set; }
    public stringLastName { get; set; }
}

[Serializable]
public classSalesReceipt
{
    publicPersonCustomer { get; set; }
}

[Serializable]
public classEmployee:Person
{
    publicDateTime DateOfHire { get; set; }
}

public classtrythis
{
    public voidmain()
    {
        Employee employee = newEmployee{ DateOfHire = DateTime.UtcNow };
        SalesReceipt receipt = newSalesReceipt();
    }
}

If you attempt to serialize the receipt object, the XML Serializer will throw the "Type Not Statically Known" exception. Basically its because the type of value in receipt.Customer isn't a Person instance, but rather a derived type (Employee).  Microsoft's solution for this may actually be worse than the XMLSerializer limitation itself.   Microsoft suggests you add an XMLInclude() attribute for every subtype you want to be able to support.  In our example this means the Person class has to be marked up as follows:


[Serializable]
[XmlInclude(typeof(Employee))]
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

 

This means every time you extend your Person class with another derivation, you have to return to the Person class and add another XMLIncludeAttribute for it.  That's pretty poor.  But what if you don't have access to the base type?  I'm not certain but I think you might be out of luck.  That's unforgivable.

 

Luckily, Microsoft does allow you to pass in a list of "known" types to the XMLSerializer's constructor.  This is the saving grace for XMLSerialization.  By reflecting the object to serialize, we can build a full list of its type, all its base types, it property and field types and all their base types.  At the end of the reflection, we ought to have every type we need to serialize the object.  The following code reflects through both the type definition and the instance definition to ensure we get all defined and instance type in our known type list.

 

 

/// <summary>
   /// reflects the object to serialize and builds a full list of related types for serialization.
   /// </summary>
   /// <param name="obj"></param>
   /// <returns></returns>
   public static StringWriter SerializeWithKnownTypes(object obj)
   {

       List<Type> knownTypeList = new List<Type>();
       // get the list of related types for this object's type and instance
       Type[] knownTypes = getKnownTypesForInstance(obj, knownTypeList);
       // create an instance of the Xml Serializer and pass in our list of related types
       XmlSerializer serializer = new XmlSerializer(obj.GetType(), knownTypes);
       // Let the serializer do its thing
       StringWriter writer = new StringWriter();
       serializer.Serialize(writer, obj);
       return writer;
   }
   private static Type[] getKnownTypesForInstance(object obj, List<Type> knownTypeList)
   {
       // get the list of known types based on the object's type definition 
       getKnownTypes(obj.GetType(), knownTypeList);

       // now we need to get the list of types from the instance, since the type definition will
       // reference the base type, and we need the actual derived type that's used in the instance
       PropertyInfo[] props = ReflectionTools.GetProperties(obj.GetType(), true);
       foreach (PropertyInfo propertyInfo in props)
       {
           // get the value of the property from the instance
           object propValue = ReflectionTools.GetPropertyValue(obj, propertyInfo.Name, false);
           if (propValue == null)
               getKnownTypes(propertyInfo.PropertyType, knownTypeList);
           else
               getKnownTypes(propValue.GetType(), knownTypeList);
       }
       return knownTypeList.ToArray();
   }

   private static Type[] getKnownTypes(Type type, List<Type> knownTypeList)
   {
       // Ignore .NET types and types already in the list
       if (type.FullName.StartsWith("System.") || knownTypeList.Contains(type))
           return knownTypeList.ToArray();

       //add this type to the known types list
       knownTypeList.Add(type);
            
       // get the list of types for this object's fields
       FieldInfo[] fields = type.GetFields();
       foreach(FieldInfo field in fields)
       {
           getKnownTypes(field.FieldType, knownTypeList);
       }
       // get the list of types for this object's properties
       PropertyInfo[] props = type.GetProperties();
       foreach (PropertyInfo prop in props)
       {
           getKnownTypes(prop.PropertyType, knownTypeList);
       }
       // if this type isn't a base type, recursively call this method for the base type 
       // collecting the type information from the base type
       if (type.BaseType != null)
           getKnownTypes(type.BaseType, knownTypeList);
       return knownTypeList.ToArray();
   }
 

The above code references a helper class called ReflectionTools for some of the less interesting work. You can substitute you own reflection code there. 

With the related types now "known" by the XmlSerializer, we can serialize the instance of our type with the XmlSerializer.

 

 

Print | posted on Thursday, January 17, 2008 1:07 AM | Filed Under [ Visual Studio ]

Feedback

Gravatar

# re: XMLSerialization - Solving the "Type Not Statically Known" exception

Great article!!
Can you tell where i can find ReflectionTool assembly?

Thanx
Marco
5/29/2008 5:12 PM | Marco
Gravatar

# re: XMLSerialization - Solving the "Type Not Statically Known" exception

The ReflectionTools mentioned in the code is just a wrapper around the system.reflection methods that extract the property names and values from a type.

Is there something specific you need help doing with reflection?
5/29/2008 10:18 PM | wtfChris
Gravatar

# re: XMLSerialization - Solving the "Type Not Statically Known" exception

Thanx for your answer.
I'm trying to solve the serialization problem when using derived class.
I've developed a class with a property of type List<BaseClass>.
In order to manage generics, i've added this line of code into your method getKnownTypes(..)

if (type.IsGenericType)
getKnownTypes(type.GetGenericArguments()[0], knownTypeList);

But the type returned from type.GetGenericArguments()[0] is always the base type, instead of the specific derived class.

I also changed the line of code referencing ReflectorTolls as follow:
YOUR LINE
PropertyInfo[] props = ReflectionTools.GetProperties(obj.GetType(), true);
WITH MINE
PropertyInfo[] props = obj.GetType().GetProperties();

YOUR LINE
object propValue = ReflectionTools.GetPropertyValue(obj, propertyInfo.Name, false);
WITH MINE
object propValue = propertyInfo.GetValue(obj, null);

Can you help me and my code?? :)

Whats going wrong?

Thanx
Marko
5/30/2008 3:25 AM | Marco
Gravatar

# re: XMLSerialization - Solving the "Type Not Statically Known" exception

Sorry, the code adedd in getKnownTypes(..) methos is this :

foreach (Type gent in type.GetGenericArguments())
{
getKnownTypes(gent, knownTypeList);
}

But alway base class is returned.
Thanx
5/30/2008 3:45 AM | Marco
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: