Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / WinMobile

XML Serialization of Generic Dictionary, Multidimensional Array, and Inherited Type, with sharpSerializer .NET

4.92/5 (48 votes)
9 Nov 2011CPOL8 min read 1   2.9K  
How to serialize to XML, any generic, derived, or other complex type, which XMLSerializer cannot serialize

Table of Contents

Introduction

sharpSerializer is an open source object serializer for .NET Framework, .NET Compact Framework and Silverlight. Its purpose is simple - quick object serialization from A to B, without security considerations. In this article, I'll try to convince you that sharpSerializer can be a lot more than the built-in XMLSerializer and is simpler.

Background

The built-in XmlSerializer has some restrictions concerning object serialization:

  • It cannot serialize multidimensional arrays.
  • It cannot serialize a generic Dictionary<TKey,TValue>.
  • It cannot serialize polymorphic properties (with an inherited value type from the property type) out of the box.
  • It needs the type of the serialized object in its Serialize(...) method.
  • It needs many attributes to define your business objects, e.g.: XmlArrayAttribute, XmlArrayItemAttribute to define the array of inherited types.

sharpSerializer overcomes the above restrictions, and serializes objects with no need to mark your objects with additional attributes and without concerning types being serialized.

Using the Code

Hello World with sharpSerializer

Let's assume we have an object:

C#
public class SomeObject
{
    public int SimpleInt { get; set; }
    public DateTime SimpleDateTime { get; set; }
    public TimeSpan SimpleTimeSpan { get; set; }
    public SimpleEnum SimpleEnum { get; set; }
    public string SimpleString { get; set; }
}

public enum SimpleEnum {One,Two,Three}

We initialize and serialize this object:

C#
// instantiate the object
var obj = new SomeObject(){
                      SimpleDateTime = new DateTime(2010,4,28),
                      SimpleEnum = SimpleEnum.Three,
                      SimpleInt = 42, 
                      SimpleString = "nothing",
                      SimpleTimeSpan = new TimeSpan(1,2,3)};

// instantiate the sharpSerializer
// with standard constructor it serializes to XML
// overloaded constructors activate binary serialization
var serializer = new SharpSerializer();

// serialize
serializer.Serialize(obj, "test.xml");

// deserialize (to check the serialization)
var obj2 = serializer.Deserialize("test.xml");

Notice! You don't need to mark your object with any attribute, nor do you have to give the object type in the Serialize method.

This object serializes to the following XML:

XML
<Complex name="Root" type="SharpSerializerTestApp.SomeObject, SharpSerializerTestApp">
  <Properties>
    <Simple name="SimpleInt" value="42" />
    <Simple name="SimpleDateTime" value="04/28/2010 00:00:00" />
    <Simple name="SimpleTimeSpan" value="01:02:03" />
    <Simple name="SimpleEnum" value="Three" />
    <Simple name="SimpleString" value="nothing" />
  </Properties>
</Complex>

As you can see, the object type was serialized as "TypeName, AssemblyName" and DateTime was serialized as CultureInfo.InvariantCulture. Later, I'll show you how to customize the type name (i.e., as AssemblyQualifiedName) and how to save DateTime and float numbers in your desired format.

Excluding Properties from the Serialization

By default, all properties are serialized, which are public, instance, and not read-only. All properties which are arrays (Type.IsArray==true), or which inherit from IEnumerable, ICollection or IDictionary are also serialized. For performance reasons, fields are not serialized.

  1. To exclude properties in your custom types, you need to mark them with attributes. ExcludeFromSerializationAttribute is supported out of the box.
    C#
    public class MyClass
    {
        [ExcludeFromSerialization]
        public int SimpleInt { get; set; }
    }
  2. If your objects are marked with common .NET Attributes such as XmlIgnore, you can add these attributes to the listing AttributesToIgnore.
    C#
    // remove default ExcludeFromSerializationAttribute for performance gain
    serializer.PropertyProvider.AttributesToIgnore.Clear(); 
    serializer.PropertyProvider.AttributesToIgnore.Add(typeof(XmlIgnore));

    or using the settings class:

    C#
    // for binary mode
    var settings = new SharpSerializerBinarySettings();
    // for xml mode
    var settings = new SharpSerializerXmlSettings(); 
    // remove default ExcludeFromSerializationAttribute for performance gain
    settings.AdvancedSettings.AttributesToIgnore.Clear(); 
    settings.AdvancedSettings.AttributesToIgnore.Add(typeof(XmlIgnore));
  3. To exclude properties of the built in .NET types, or if you cannot extend properties with attributes, you add the type and its property name to the list SharpSerializer.PropertyProvider.PropertiesToIgnore.

    i.e. System.Collections.Generic.List<T> has property Capacity which is irrelevant for the serialization, thus it should be ignored.
    C#
    serializer.PropertyProvider.PropertiesToIgnore.Add
    	(typeof(List<string>), "Capacity");

    PropertiesToIgnore can be also accessed from the sharpSerializer settings.

    C#
    // create the settings
    var settings = new SharpSerializerBinarySettings(); // for binary mode
    var settings = new SharpSerializerXmlSettings(); // for xml mode
    settings.AdvancedSettings.PropertiesToIgnore.Add(typeof(List), "Capacity");

Customizing the Property List for Serialization

If it is not enough to filter properties with ExcludeFromSerializationAttribute, AttributesToIgnore or PropertiesToIgnore, you can build your CustomPropertyProvider. As a base class, there is PropertyProvider, which has two virtual methods GetAllProperties() and IgnoreProperty(...). They can be overwritten to customize the logic.

C#
serializer.PropertyProvider = new MyCustomPropertyProvider();

Serializing type as AssemblyQualifiedName or as a short type name "TypeName, AssemblyName"

Before SharpSerializer v.2.12, all types were serialized as short type name "TypeName, AssemblyName"; i.e.:

C#
type="System.String, mscorlib"

This was simple to read, output size was small. But it encountered problems during deserialization if working with signed assemblies or their specific versions.

Since SharpSerializer v.2.12 are all types serialized as AssemblyQualifiedName; i.e.:

C#
type="System.String, mscorlib, Version=2.0.0.0, 
      Culture=neutral, PublicKeyToken=b77a5c561934e089"

You can alter the type naming by altering settings of sharpSerializer and setting the properties IncludeAssemblyVersionInTypeName, IncludeCultureInTypeName and IncludePublicKeyTokenInTypeName. As default, these properties are set to true.

C#
var settings = new SharpSerializerXmlSettings();

settings.IncludeAssemblyVersionInTypeName = false;
settings.IncludeCultureInTypeName = false;
settings.IncludePublicKeyTokenInTypeName = false;

var serializer = new SharpSerializer(settings);

Custom Formatting of DateTime and Float Values

By default, all primitive types and DateTime are converted to strings according to CultureInfo.InvariantCulture. If custom formatting or a culture is needed, this can be achieved with the following modification of the settings class:

C#
var settings = new SharpSerializerXmlSettings();
settings.Culture = System.Globalization.CultureInfo.CurrentCulture;
var serializer = new SharpSerializer(settings);

Serialize Data to Other Format as XML

Actually, sharpSerializer can serialize to XML and to its own binary format. However, after injecting it with custom IXmlWriter or IBinaryWriter it can serialize data to other text formats like Json or other encrypted, compressed, optimized, etc. binary streams. Please refer to the project site for details concerning binary serialization.

How Does sharpSerializer Work?

It works in three steps:

Step 1

The PropertyFactory converts an object to the Property. The abstract class Property contains PropertyCollection. PropertyCollection represents the object structure and its data. The following classes inherit from the Property class:

  • SimpleProperty for describing all primitive types and string, DateTime, TimeSpan and all enumerations
  • ComplexProperty for other classes and structures which are not listings
  • ComplexReferenceProperty for referencing a class which was already serialized (it saves space.)
  • SingleDimensionalArrayProperty for single dimensional arrays
  • MultiDimensionalArrayProperty for multidimensional arrays
  • CollectionProperty for listings which inherit from ICollection but not from IDictionary
  • DictionaryProperty for listings which inherit from IDictionary
  • NullProperty for all objects/strings which are null (there is a need to make a notice that they are null)

Step 2

Property is serialized by XmlPropertySerializer. In which format it is serialized depends on the used writer.

Step 3

For XML serialization, DefaultXmlWriter is responsible. But as a sink can be used any writer, which implements IXmlWriter. In this way, you can write your own writers which serialize data to other formats like Json.

During deserialization, the process is inversed. An instance of IXmlReader reads the data and forwards it to the XmlPropertyDeserializer, which deserializes the data into Property. Finally, ObjectFactory converts Property into object.

Please download the source code for more details.

Examples of Advanced Serialization

Following examples are parts of the HelloWorldDemo application which you can download with the source code.

Serialize a Polymorphic Property (where Property Value is Inherited from the Property Type)

There is a property of type IComplexObject. The property value contains a class ComplexObject which is inherited from IComplexObject.

C#
public IComplexObject ComplexObject { get; set; }

There is no need to input the type being serialized. sharpSerializer can serialize the expected type and any inherited type out of the box:

XML
<Complex name="ComplexObject" 
     type="HalloWorldApp.BusinessObjects.ComplexObject, HalloWorldApp">
  <Properties>
    <Simple name="SimpleInt" value="33" />
  </Properties>
</Complex>

Serialize a Generic Dictionary where Values are Inherited from the Interface

There is a generic dictionary where values are of some complex class which is inherited from the interface IComplexObject (polymorphic argument):

C#
public IDictionary<int, IComplexObject> GenericDictionary { get; set; }

It serializes to:

XML
<Dictionary name="GenericDictionaryOfPolymorphicValues" 
          type="System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],
                   [HalloWorldApp.BusinessObjects.IComplexObject, HalloWorldApp]], 
                   mscorlib" keyType="System.Int32, mscorlib" 
          valueType="HalloWorldApp.BusinessObjects.IComplexObject, HalloWorldApp">
    <Items>
      <Item>
        <Simple value="2012" />
        <Complex type="HalloWorldApp.BusinessObjects.ComplexObject, HalloWorldApp">
          <Properties>
            <Simple name="SimpleInt" value="2012000" />
          </Properties>
        </Complex>
      </Item>
    </Items>
</Dictionary>

As you can see, the type of keys and values does not matter. sharpSerializer can serialize primitive types, complex objects, and even nested listings, e.g., dictionaries.

Serialize a Multidimensional Array

There is a two dimensional array (it could have more dimensions):

C#
public string[,] DoubleArray { get; set; }

It serializes to:

XML
<MultiArray name="DoubleArray" elementType="System.String, mscorlib">
    <Dimensions>
      <Dimension length="3" />
      <Dimension length="2" />
    </Dimensions>
    <Items>
      <Item indexes="0,0">
        <Simple value="k1" />
      </Item>
      <Item indexes="0,1">
        <Simple value="k2" />
      </Item>
      <Item indexes="1,0">
        <Simple value="b1" />
      </Item>
      <Item indexes="1,1">
        <Simple value="b2" />
      </Item>
      <Item indexes="2,0">
        <Simple value="z1" />
      </Item>
      <Item indexes="2,1">
        <Simple value="z2" />
      </Item>
    </Items>
</MultiArray>

It does not matter of what type the array is. sharpSerializer can serialize an array of any object, array of arrays, array of collections, or an array of dictionaries. It can serialize really deep nested arrays.

Serialize an Array of Different Object Types and Array of Arrays (Nested Array)

There is an array of the following objects:

C#
root.SingleArrayOfObjects = new object[] 
{
    42, 
    "nothing to say", 
    false, 
    BusinessObjects.SimpleEnum.Three, 
    null, 
    new object[] 
           {
              42, 
              "nothing to say", 
              false, 
              BusinessObjects.SimpleEnum.Three, 
              null 
           } 
};

It serializes to:

XML
<SingleArray name="SingleArrayOfObjects" elementType="System.Object, mscorlib">
    <Items>
      <Simple type="System.Int32, mscorlib" value="42" />
      <Simple type="System.String, mscorlib" value="nothing to say" />
      <Simple type="System.Boolean, mscorlib" value="False" />
      <Simple type="HalloWorldApp.BusinessObjects.SimpleEnum, 
		HalloWorldApp" value="Three" />
      <Null />
      <SingleArray type="System.Object[], mscorlib" 
		elementType="System.Object, mscorlib">
        <Items>
          <Simple type="System.Int32, mscorlib" value="42" />
          <Simple type="System.String, mscorlib" value="nothing to say" />
          <Simple type="System.Boolean, mscorlib" value="False" />
          <Simple type="HalloWorldApp.BusinessObjects.SimpleEnum, HalloWorldApp" 
        value="Three" />
          <Null />
        </Items>
      </SingleArray>
    </Items>
</SingleArray>

Serialize Other Types

Please download the source code to see the whole example and what else can be serialized. Below are some other properties from the HelloWorldDemo:

C#
public class RootContainer
{
    /// <summary>
    /// Structures are handled as objects during serialization
    /// They are serialized as ComplexProperty
    /// </summary>
    public AdvancedStruct AdvancedStruct { get; set; }

    /// <summary>
    /// Single dimensional array of simple type.
    /// It is serialized as SingleDimensionalArrayProperty
    /// </summary>
    public string[] SingleArray { get; set; }

    /// <summary>
    /// Multidimensional array of simple type.
    /// Is is serialized as MultiDimensionalArrayProperty
    /// </summary>
    public string[,] DoubleArray { get; set; }

    /// <summary>
    /// Single array of derived objects.
    /// This is polymorphic collection - Items derive from the interface
    /// </summary>
    public IComplexObject[] PolymorphicSingleArray { get; set; }

    /// <summary>
    /// Generic list is serialized as a collection.
    /// It is serialized as CollectionProperty
    /// </summary>
    public IList<string> GenericList { get; set; }

    /// <summary>
    /// Polymorphic property. Object instance derives from the property type
    /// Is serialized as ComplexProperty
    /// </summary>
    public IComplexObject ComplexObject { get; set; }

    /// <summary>
    /// Collection where item values are
    /// derived from the collection item type
    /// </summary>
    public ComplexObjectPolymorphicCollection ComplexObjectCollection
                { get; set; }

    /// <summary>
    /// Dictionary where values are derived
    /// from the predefined dictionary value type
    /// </summary>
    public ComplexObjectPolymorphicDictionary ComplexObjectDictionary 
                { get; set; }

    /// <summary>
    /// List items are derived from the generic attribute.
    /// This is polymorphic attribute.
    /// </summary>
    public IList<IComplexObject> GenericListOfComplexObjects { get; set; }

    /// <summary>
    /// Generic object with polymorphic attribute.
    /// It is serialized as ComplexProperty
    /// </summary>
    public GenericObject<IComplexObject> GenericObjectOfComplexObject
                { get; set; }

    /// <summary>
    /// Multidimensional array of generic object with polymorphic attribute
    /// </summary>
    public GenericObject<IComplexObject>[,] 
	MultiArrayOfGenericObjectWithPolymorphicArgument
                { get; set; }
}

public interface IComplexObject { int SimpleInt { get; set; } }

public class ComplexObject : IComplexObject {public int SimpleInt
                { get; set; }}

public class ComplexObjectPolymorphicCollection :
                Collection<IComplexObject>{}

public class ComplexObjectCollection :
                Collection<ComplexObject>{}

public class ComplexObjectPolymorphicDictionary :
                Dictionary<int, IComplexObject>{}

public class ComplexObjectDictionary :
                Dictionary<int, ComplexObject>{}

What do you think? Is XML serialization with sharpSerializer simple enough?

Restrictions of sharpSerializer

In the current version of sharpSerializer, there are some restrictions concerning serialization and deserialization of objects. In the future, these restrictions can be neutralized, but actually they make no such pain. These are:

  • Objects without their public standard constructor cannot be deserialized.
  • Multiple references to the same complex object are not optimized. Such multiple referenced object is serialized as many times as many references to it. (SharpSerializer v.2.9 and above can optimize serializing of multiple references to the same object. Such an object is serialized only once. This optimization results with smaller file size.)

Following limitation concerns serialization in .NET Compact Framework and Silverlight:

  • LowerBound in an array will always be deserialized as 0 regardless of how it was serialized.

LowerBound of an array is not a part of .NET Compact Framework or Silverlight and therefore cannot be handled by the sharpSerializer.

Usage Scenarios

The main reason why I developed sharpSerializer was to save application configuration in an XML file. Few years ago, I was struggling with a polymorphic configuration. I needed a lightweight configuration storage with support for object inheritance. The file should be easily readable and manually editable. The System.Configuration has a big overhead and is too stiff.

The second reason is simple - Silverlight has poor support for the local data repository - it should have a better one :-).
Especially interesting is its possibility to serialize data to the binary format in WP7 (Windows Phone 7).

Download Current Sources

The most recent sources and news are on the project page: www.sharpserializer.com.

Author's Note

If you like sharpSerializer or this article - please rate it. If not, please make a comment below ;-)

History

  • 2011-11-09: Changes according to sharpSerializer v.2.16
  • 2011-10-24: Updated download files
  • 2011-07-31: Updated download files SharpSerializer v.2.12
  • 2011-07-28: Updated download files sharpSerializer v.2.11
  • 2011-05-08: Changes according to sharpSerializer v.2.9, Guid serialization, AttributesToIgnore and optimized serialization of multiple references to the same object
  • 2010-10-07: Updated download files
  • 2010-10-03: Changes according to sharpSerializer v.2.0
  • 2010-05-05: Changes according to sharpSerializer v.1.2
  • 2010-05-04: Added support for the .NET Compact Framework
  • 2010-04-30: Formatting changes (cut off some parts of the big XML example)
  • 2010-04-29: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)