Serializing Complex Types
If you want to serialize and deserialize your own types that can’t be serialized out of the box (as mentioned in Automatically Serialized Types), for example your business model or just your custom styles implementation, you need to explicitly provide means to serialize and deserialize them.
There are three different ways to provide serialization support for your classes.
Markup Extension
Markup extensions use the fact that the (de)serialization mechanism in yFiles for HTML uses reflection to discover readable and writable properties of classes and set their values.
A markup extension can be used to support (de)serialization for types which do not fulfill all prerequisites to be (de)serialized themselves. It does so by adapting the class it handles to expose all values to be serialized as read/write properties.
A markup extension is a class that derives from the abstract base class MarkupExtension. Creating one enables both the serialization and deserialization of its adapted class. When encountering an XML element of a class that is represented by a markup extension, the framework fills the properties of the markup extension with the values of the XML element. Afterwards it calls the abstract method provideValue and expects the markup extension to return an object of the desired class, given the values that are set in the properties.
Registering a Markup Extension for Deserialization
If the markup extension is required for deserialization, it needs to be registered with addXamlNamespaceMapping. The markup extension’s XML tag name must be the represented type’s XML tag name plus "Extension".
Registering a Markup Extension for Serialization
If the markup extension is required for serialization as well, you need to tell the framework that your particular class is represented by your markup extension for serialization. For this, you need to provide a way to acquire a markup extension from your custom type. This is accomplished by implementing the IMarkupExtensionConverter interface.
- In its canConvert
method return
true
for objects that the markup extension can handle. - In the convert method create and return your markup extension properly configured for the given object.
Finally, you need to provide your IMarkupExtensionConverter for your object:
- You can either annotate your custom class with the GraphMLAttribute annotation and set its GraphMLAttribute.markupExtensionConverter property to the class of the IMarkupExtensionConverter implementation,
- if your object implements ILookup return the IMarkupExtensionConverter in the lookup method as described in the Service Locator Pattern: Lookup section,
- or implement the IMarkupExtensionConverter interface directly on your object.
The A sample class definition with a read-only property and a markup extension that adapts this class example shows a sample class definition for objects that we want to write to and read from GraphML along with the graph structure. The class has only one property, which is read-only and can only be set by the constructor. Thus objects of the class cannot be handled directly by XML serialization/deserialization which needs writable properties. As a consequence, we have implemented a markup extension which will be used for the serialization process instead. The markup extension overcomes the class’s limitations by exposing the property as read/write property and by providing a default constructor.
How an implementation of IMarkupExtensionConverter for the sample class and markup extension might look like is shown in the example An implementation of IMarkupExtensionConverter for the sample class.
As previously mentioned, if you have the source code of the class you want to serialize, there are different ways to let the framework use the markup extension. The easiest way is to annotate the sample class directly and passing the IMarkupExtensionConverter implementation in the annotation as shown in the example Inserting the IMarkupExtensionConverter by annotating the sample class.
Similar to this example, it is also possible to annotate single properties with attributes.
Another way is to implement ILookup on the sample class and return the IMarkupExtensionConverter implementation in the lookup method as shown in example Inserting the IMarkupExtensionConverter by implementing ILookup.
You can as well let the class itself return the markup extension by implementing the IMarkupExtensionConverter interface directly.
In cases where you can’t alter the source code of the class to serialize, you have to insert the MarkupExtension in the serialization process yourself. This can be done by registering an event handler to the GraphMLIOHandler.HandleSerialization event. The crucial part here is to call the IWriteContext.serializeReplacement<T> method within the listener and replace the object to serialize with a markup extension for the object. The example Manually inserting the MarkupExtension in the serialization process shows how this can be implemented.
The GraphMLIOHandler.HandleSerialization event enables customization of the entire serialization process. This event, along with its counterpart for deserialization and the possibilities for customizations using these events are described in detail in the section Serialization Event Listeners.
Serialization Event Listeners
The GraphMLIOHandler.HandleSerialization and GraphMLIOHandler.HandleDeserialization events provide low-level hooks for customizing the (de)serialization process. They are dispatched each time the GraphML writing mechanism encounters an object to serialize or an XML element to deserialize.
They can be used in cases where objects need to be serialized that cannot be altered (as mentioned in the section Markup Extension) or where developers want to have full control over the resulting output.
A serialization handler is an event handler registered on the
aforementioned events. If the object to be serialized can be handled, the provided XML writer has to be used
to write XML representing the object. Afterwards, the handled flag has to be set to true
so no other
handler will be queried.
You need to register your custom serialization handlers using HandleSerialization.
A deserialization handler is an event handler for events which are dispatched each time the GraphML parsing mechanism retrieves an XML element that is about to be deserialized. If the handler recognizes the XML element as XML it can handle (usually if both the element’s name and namespace are matching), it has to create an instance of the class it handles based on the XML element. The created object has to be set as result which automatically sets the event as handled, preventing any subsequent handler from being invoked.
You need to register your custom deserialization handlers using HandleDeserialization.
Serialization with Symbolic Names
It is possible to use symbolic names for the output file format instead of XML elements that encode the information of an object. Using this approach, you shift the responsibility to encode the information from the file format to the application. These so-called external references are specified by symbolic names. To this end, event handlers that provide the symbolic names may be registered using method QueryReferenceId as shown below:
The symbolic name is specified using the referenceId
property provided by the event argument’s type that is handed over to the event handler.
It is then used in GraphML in the <data>
element as shown below:
When reading in the GraphML representation of a graph, an application can be notified of external references by registering event handlers using method ResolveReference. Corresponding events are triggered for references that cannot be resolved, which is especially true for external references.