nosjob  s11n

JSON-based Native Object Serialization

"s11n" is a short form of the word "serialization", as described on the libs11n home page at The nosjob::s11n namespace holds a stripped-down version of libs11n which can serialize "native objects" (of non-JSON classes like std::map) to/from JSON. It uses templates to ensure compile-time type checking where possible, and is client-extensible by providing a couple of template specializations customized for serializing client-side native types.

Here's an example:

typedef std::map<int,double> Map;
Map map;
// populate map ...
for( int i = 0; i < 5; ++i ) {
    map[i] = i * 1.1;
// Then serialize it to JSON ...
// Approach #1:
// Serialize and save in one step:
nosjob::s11n::save( map, std::cout );
// Approach #2:
// First serialize:
Array target;
nosjob::s11n::serialize( target, map );
// Then save:
nosjob::s11n::save( target, std::cout );

The output (reformatted for readability):

[ {"k":0, "v":0.0}, {"k":1, "v":1.1},
  {"k":2, "v":2.2}, {"k":3, "v":3.3},
  {"k":4, "v":4.4}

Note that when serializing a std::map, we have to add an extra level of indirection (a list of key/value pairs instead of an Object) in order to keep as much type information as possible. In JSON, all keys are string, which means we cannot use JSON Objects to serialize maps with arbitrary key types. We do not have this extra layer with std::list and similar types.

To load a deserialized object:

Map map;
bool ok = s11n::load( map, anInputStream );
// load() also takes an input iterator range as a source.
// load() might throw on error.

Notable Features

  • Trivial API: a small handful of functions.
  • Compile-time type-safe wherever possible. Some conversions cannot happen until runtime, and exceptions are thrown for this where it makes sense to do so.
  • Can be "trained" to serialize client-side custom types. That means specializing two class templates, each with one operation. Documenting how to do this is on the TODO list, but the sources contain numerous examples (see the NativeToAtom and AtomToNative classes) which handle the most common STL containers.
  • Once the library is "trained" for a given type, that type can be used in any serialization contexts. e.g. the type can then be de/serialized as a value type in a serializable container.

Notable Limitations

The more notable limitations include:

  • Relatively memory hungry. It serializes natives to a tree/list of Atoms, then streams that tree/list to an output destination. While short-lived, the tree/list will have a size relative to the native object's size. For deserialization the situation is the same: we deserialize the JSON to an Object/Array, then fish the data out of there to restore a native object's state.
  • It cannot do polymorphic deserialization (dynamically figuring out which subclass to load for a given concrete native base type). Adding this requires adding a factory layer, and while that it not much work to do (we have the code in libs11n), it adds complications to the framework (and output data structure) which would seem to be unnecessary for this limited form of serialization.
  • Related to the previous point, it has a few limitations regarding pointerness. e.g. it cannot directly deserialize a std::map<K*,V> because it does not know how to allocate the key-type pointers, nor does it know how to manage their lifetimes.
  • Directly supports only deep-copy serialization, and does not inherently support graphs. Any cycles in to-be-serialized objects may cause it to eventually die deep inside an endless recursion. Serializing graphs affects the JSON structure and requires type-specific knowledge which this code does not have.