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.
The MonoDevelop.Core.Serialization namespace defines several attributes which can be used to control the way data is serialized.
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 |
[DataItem ("DataTest")]
public class SerializationTest
{
...
} | <DataTest> ... </DataTest> |
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:
For example:
| Class | Serialized Data |
public class SerializationTest
{
// This field won't be serialized
int someInt;
// This field will be serialized
[ItemProperty]
string someString = "Hi";
// This field will be serialized with a custom name
[ItemProperty ("CustomName")]
int value = 44;
// This field will be serialized, but only if its value
// is not "Bye" (so it won't be serialized in this example)
[ItemProperty (DefaultValue = "Bye")]
string someDefaultString = "Bye";
// This field will be serialized as a child element of "Data"
[ItemProperty ("Data/Info")]
string someInfo = "some info";
} | <SerializationTest
someString = "Hi"
CustomName = "44">
<Data Info = "some info" />
</SerializationTest> |
The DataSerializer will serialize an object as a collection of items if:
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 |
public class SerializationTest
{
// A simple collection of strings
[ItemProperty ("Names")]
List<string> names = new List<string> ();
public SerializationTest ()
{
names.Add ("Jordi");
names.Add ("Maria");
}
} | <SerializationTest>
<Names>
<String>Jordi</String>
<String>Maria</String>
</Names>
</SerializationTest> |
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 |
public class SerializationTest
{
// A collection of strings with a custom name for the elements
[ItemProperty ("ExtraNames")]
[ItemProperty ("Name", Scope="*")]
List<string> extraNames = new List<string> ();
// This property will be serialized as a collection of strings.
// The element name will be "Objects" (instead of "array"),
// and there will be an "Id" element for each item of the list.
// Also, the ValueType property is set to tell the serializer
// that the elements of the collection are strings.
[ItemProperty ("Objects")]
[ItemProperty ("Id", Scope="*", ValueType=typeof(string))]
ArrayList array = new ArrayList ();
public SerializationTest ()
{
extraNames.Add ("Jordi");
extraNames.Add ("Maria");
array.Add ("Jordi3");
array.Add ("Maria3");
}
} | <SerializationTest>
<ExtraNames>
<Name>Jordi</Name>
<Name>Maria</Name>
</ExtraNames>
<Objects>
<Id>Jordi3</Id>
<Id>Maria3</Id>
</Objects>
</SerializationTest> |
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 |
public class SerializationTest
{
// When using nested collections, the scope can be aggregated
[ItemProperty ("User", Scope="*")]
[ItemProperty ("Name", Scope="*/*")]
List<List<string>> nestedNames = new List<List<string>> ();
public SerializationTest ()
{
List<string> nested1 = new List<string> ();
nested1.Add ("Jordi1");
nested1.Add ("Maria1");
nestedNames.Add (nested1);
List<string> nested2 = new List<string> ();
nested2.Add ("Jordi2");
nested2.Add ("Maria2");
nestedNames.Add (nested2);
}
} | <SerializationTest>
<nestedNames>
<User>
<Name>Jordi1</Name>
<Name>Maria1</Name>
</User>
<User>
<Name>Jordi2</Name>
<Name>Maria2</Name>
</User>
</nestedNames>
</SerializationTest> |
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 |
public class SerializationTest
{
[ItemProperty]
int SomeInteger = 32;
// This property will be serialized as a set of Value elements
// There won't be a "values" root element.
[ItemProperty ("Value")]
[ExpandedCollection]
string[] values;
public SerializationTest ()
{
values = new string[] { "One", "Two" };
}
} | <SerializationTest SomeInteger="32">
<Value>One</Value>
<Value>Two</Value>
</SerializationTest> |
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 |
public class SerializationTest
{
// A simple dictionary, with no customizations
[ItemProperty]
Dictionary<string,int> data = new Dictionary<string,int> ();
// A dictionary containing objects, using custom names
// for the items, keys and values
[ItemProperty ("Users")]
[ItemProperty ("User", Scope="item")]
[ItemProperty ("Id", Scope="key")]
[ItemProperty ("Data", Scope="value")]
Dictionary<string,User> userData = new Dictionary<string,User> ();
public SerializationTest ()
{
data ["One"] = 1;
data ["Two"] = 2;
userData ["jd"] = new User ("Jordi", "jordi@here.com");
userData ["ma"] = new User ("Maria", "maria@here.com");
}
}
public class User
{
[ItemProperty ("Name")]
string name;
[ItemProperty ("EMail")]
string email;
public User (string name, string email) { ... }
} | <SerializationTest>
<data>
<Item Key="One" value="1" />
<Item Key="Two" value="2" />
</data>
<Users>
<User Id="jd">
<Data Name="Jordi" EMail="jordi@here.com" />
</User>
<User Id="maria">
<Data Name="Maria" EMail="maria@here.com" />
</User>
</data>
</SerializationTest> |
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:
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 = serialize.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);
}
}