Using The Data Serializer
MonoDevelop provides a general purpose data serializer which can be used by add-ins to serialize data to files. This serializer is used by the project service to read and write projects and solutions, but it can be used to store all kinds of information. This API is available in the MonoDevelop.Core.Serialization namespace. The serializer is implemented in the DataSerializer class.
DataSerializer is similar to the core XML serializer, in which the process of serialization can be controlled by applying some attributes to fields and properties. The main difference is that the data serializer does not directly generate XML, but data nodes. A DataNode can be either a DataValue (a key/value pair) or a DataItem (a collection of DataNode).
The DataNode/DataValue/DataItem model is more or less equivalent to the XmlNode/XmlAttribute/XmlElement model. It is possible to directly serialize data into XML, using the XmlDataSerializer class. This class serializes an object into XML by converting DataNode objects to XmlNode objects. So DataItems will be serialized as XmlElements and DataValue objects as XmlAttribute. The XmlConfigurationWriter and XmlConfigurationReader classes allow more fine grained serialization options.
Designing Serializable Types
The MonoDevelop.Core.Serialization namespace defines several attributes which can be used to control the way data is serialized.
Making a Class Serializable
There is no special attribute to be applied to a class to make it serializable. The only requirement for a class to be serializable is to have a default constructor (which can be private).
The DataItemAttribute attribute can be used to set the name of the type in the serialized data model. By default it’s the name of the class. This attribute is optional. Here is an example of how this attribute can be used:
Class | Serialized Data |
|
|
Fields and Properties
An important difference between DataSerializer and other serializers is that fields and properties are not serialized by default. For a field or property to be serialized, the ItemPropertyAttribute attribute has to be explicitly applied to it.
The serializer generates a DataNode for each serializable member. This DataNode will be a DataValue if the member has a primitive value, or a DataItem if the member is a class (members of the class will be serialized as children of that DataItem).
ItemPropertyAttribute has several properties (all of them optional) that can be set to change the default serialization behavior:
- Name: Name of the property in the serialized model. It’s the member name by default. You can specify a nested element name. For example, the name “Data/Name” will create the property as a child of the Data element, and it will be named “Name”.
- DefaultValue: Default value of the member. A property won’t be serialized if the assigned value is the default value.
- ReadOnly: true if the member will be read but never written.
- WriteOnly: true if the member will be written but never read.
- Scope: When added to a collection or array member, it specifies the nesting level to which the ItemPropertyAttribute applies. This is explained in more detail below.
- ValueType: Specifies the type to use for serialization, if the one declared in the member is too generic.
For example:
Class | Serialized Data |
|
|
Serializing Lists
The DataSerializer will serialize an object as a collection of items if:
- the object is an array
- the type of the object implements IList, with a public Add method.
Lists are serialized as a DataItem which has a DataNode for each element of the list. The root DataItem can be customized using the ItemPropertyAttribute just like any other member of a type.
For example:
Class | Serialized Data |
|
|
Customizing the Serialization of Collection Elements
By default, elements of the list are serialized using the type name of the element type as the name for the DataNode. It is also possible to customize the serialization of the list elements by using the ItemPropertyAttribute. In this case, the Scope attribute has to be used to specify that the attribute applies to the elements of the list, not to the list as a whole.
To specify that an ItemPropertyAttribute applies to the elements of a list, the Scope must be set to ”*“. Here are some examples:
Class | Serialized Data |
|
|
Nested Scopes
When serializing nested collections, several ItemPropertyAttribute attributes can be applied using different scopes, one for each nesting level. The scope for the first level of nesting is “*”, the scope for the second level is “*/*”, for the third “*/*/*” and so on:
Class | Serialized Data |
|
|
Collections Without a Root Item
When the ExpandedCollectionAttribute attribute is applied to a collection, the root item of the collection will not be serialized. The elements of the collection will be serialized as direct children of the class item.
The ItemPropertyAttribute can be used to customize the serialization of the elements, although the use of the Scope property is not required here.
Class | Serialized Data |
|
|
Serializing Dictionaries
The serializer can serialize objects implementing the IDicionary interface. Dictionaries are serialized as a DataItem node which has a list of dictionary entries as children. Each dictionary entry is also a DataItem (named Item by default) which has two child items: the Key item, which holds the key of the entry, and the Value item, which holds the value of the entry.
The scopes ‘item’, ‘key’ and ‘value’ can be used to customize the serialization of the corresponding elements.
Some examples:
Class | Serialized Data |
|
|
Custom serialization
Types with complex serialization needs which can’t be specified using attributes can implement ICustomDataItem to provide a custom serialization behavior. ICustomDataItem defines two methods:
- Serialize: This method has to serialize the contents of the object into a DataCollection. It provides as input parameter an ITypeSerializer instance which you can use to run the default serializer.
- Deserialize: It has to deserialize the provided DataCollection and fill the object with data. An ITypeSerializer is also provided.
Here is an example:
public class WindowData: ICustomDataItem
{
[ItemProperty]
string id;
[ItemProperty]
string title;
int width;
int height;
DataCollection ICustomDataItem.Serialize (ITypeSerializer serializer)
{
// Use the provided serializer to run the default serialization.
// This is optional, but it's useful if you just want to
// tweak the default behavior, not completely reimplement it.
DataCollection data = serializer.Serialize (this);
// Now add some custom data
data.Add (new DataValue ("size", width + " " + height));
return data;
}
void ICustomDataItem.Deserialize (ITypeSerializer serializer, DataCollection data)
{
// Extract the custom value we added in the Serialize method
DataValue size = data.Extract ("size") as DataValue;
string[] sizes = size.Value.Split (' ');
width = int.Parse (sizes[0]);
height = int.Parse (sizes[1]);
// Deserialize the other fields.
serializer.Deserialize (this, data);
}
}