s11n.net
s11nlite::save(Anything, Anywhere);
Project powered by:
SourceForge.net

Sample client-side s11n code


Here we show a basic sample of a client-defined "Serializable Type" using the 0.9.x interface.

Client Serializable classes must implement the so-called "Serializable Interface" or have a "Serializable Proxy" installed to do it for them (RTFM). The sample below shows the "intrusive" approach. The non-intrusive, proxy-based approach is shown in later examples.

The whole process of serialization, and where client classes fit into it all, is all covered in detail in the library manual.

Consider FooClass.h, the header file for our class FooClass:
#include <s11n.net/s11n/s11nlite.hpp> // s11nlite framework
// ... any other necessary headers ...
class FooClass
{
public:
// The class must implement two so-called "serialization
// operators", or they must be proxied via a registered
// proxy or alternate "compatible" local interface (RTFM).
// serialize:
bool operator()( s11nlite::node_type & src ) const;
// deserialize:
bool operator()( const s11nlite::node_type & dest );
// any other client-side functions...
private:
// some data members we would like to make persistant:
// a simple string:
std::string m_name;
// a vector of integers:
std::vector<int> m_vec;
// a map of int-to-string:
std::map<int,std::string>; m_map;
// a single child pointer (could be of any Serializable
// type):
FooClass * m_child;
// a list of children (again, could be any Serializable
// child type):
std::vector<FooClass *>; m_children;
};
// very important:
// 0.9.x registration technique:
#define S11N_TYPE FooClass
#define S11N_TYPE_NAME "FooClass"
#include <s11n.net/s11n/reg_serializable_traits.hpp>
That last line is of the utmost importance, as it tells the framework how to deal with FooClass. (That part has a LOT of documentation...) In short, this does some back-door work like classloader registration and making sure that FooClass's Serializable-related API are properly mapped into the framework.

In this simple case there is nothing more we need to do to make s11n aware of FooClass as a Serializable Type.

Before we go on, let's cover some important things to know about s11n's "serialization operators":
  • They represent abstract s11n concepts and conventions, not an exact function signature.
  • They heed not be virtual, but may be so.
  • They may be function templates. (Once you understand the s11n framework you will see some benefits in making them so, at least when proxying class templates.)
  • They may have different names and/or signatures, but this requires a bit of client-side work: a (very small) proxy class must be written to translate the s11n API calls to the type's local API.
  • They can be transparently proxied via a functor, for types which cannot be edited. This does not change client-side usage of the type as a Serializable, only the way it is registered with s11n.
  • There are lots of options. RTFM for details.
Now we implement FooClass' s11n-related interface:
(Updated 11 Aug 2004 for 0.9.4)
#include "FooClass.h"
#include <s11n.net/s11n/list.hpp> // list-related serialization functions
#include <s11n.net/s11n/map.hpp> // map-related serialization functions
// Our so-called "serialize operator" serializes
// this object into a Data Node:
bool FooClass::operator()( s11nlite::node_type & node ) const
{
typedef s11n::node_traits<s11nlite::node_type> NT;
NT::class_name( node, "FooClass" );
NT::set( node, "name", this->m_name );
s11n::list::serialize_list( node, "vector", this->m_vec );
s11n::list::serialize_list( node, "children", this->m_children );
s11n::map::serialize_map( node, "themap", this->m_map );
if( this->m_child ) s11n::serialize_subnode( node, "child", *(this->m_child) );
return true;
}
// Our so-called "deserialize operator" deserializes
// this object from a Data Node:
// Note that this is almost a mirror-image of the serialize
// operator.
bool FooClass::operator()( const s11nlite::node_type & node )
{
// now get our data:
typedef s11n::node_traits<s11nlite::node_type> NT;
this->m_name = NT::get( node "name", "no name was set :`(" );
s11n::list::deserialize_list( node, "vector", this->m_vec );
s11n::list::deserialize_list( node, "children", this->m_children );
s11n::map::deserialize_map( node, "themap", this->m_map );
this->m_child = s11nlite::deserialize_subnode<FooClass>( node, "child" );
// ^^^ achtung: child might be null, and we may need to recover from that
// case. That is necessarily client-dependent.
return true;
}


Several working examples can be found in the s11n source tree: src/client/sample/*.cpp.

Specific de/serialize implementations can, of course, get much more complex, but the general approach is just as straightforward as shown here, pretty much regardless of the type of data being de/serialized. Template methods do all of the type-juggling for the client, removing a lot of the work traditionally associated with serialization. Also, deserialization is almost always a mirror-image of serialization, so the interface is fairly simple to get used to.

A serializable interface can be further customized, e.g. to accept more arguments, but that is generally not necessary, and requires a good understanding of the underlying architecture.

Side-note: error handling
The above example has a distinct lack of error checking for three reasons:
  1. Brevity - they would make the examples less readable.
  2. The exact meaning of "error" is necessarily client-dependent. e.g., in some cases a missing child node or property is an error and in others it is not.
  3. In practice, errors are much more likely to happen at the parsing or file i/o levels, far away from these functions. It is particularly unlikely that serialization will fail, as opposed to deserialization.
Error handling is covered in more detail in the library manual.

And now the good part...

After the above code is in place, FooClass becomes a full-rights player in the s11n framework, and can be de/serialized via the same easy interface used for all Serializable Types. (What does that really mean? RTFM. :)

Now let's save a FooClass. There are many different options for how to do this. The simplest is:
#include <s11n.net/s11n/s11nlite.hpp>
...
FooClass myfoo;
...
bool worked = s11nlite::save( myfoo, "somefile.s11n" );
Easy, eh?

As with saving, for loading there are several options, but this is the simplest:
FooClass * myfoo =
      s11nlite::load_serializable<FooClass>( "somefile.s11n" );
Or we can deserialize directly into an existing FooClass object, as opposed to creating a new one as in the above example:
s11nlite::node_type * node = s11nlite::load_node( "somefile.s11n" );
if( ! node ) { ... error loading data ... }
bool worked = s11nlite::deserialize( *node, myfoo );
For many cases, the FooClass sample will do as a basis for serializing client-side types. There are easy-to-implement options for making a type Serializable without editing it (without it even knowing it's participating!). The documentation goes into much more detail about the topic of how to implement a Serializable interface, and examples exist in the source tree: src/client/sample/*.cpp.