Sans Pareil Technologies, Inc.

Key To Your Business

BSON C++ API



Sans Pareil Technologies, Inc. has developed a stand-alone C++ API for working with BSON data (also on freecode). The API is modelled after the DOM style API developed for the MongoDB Viewer desktop application. The goals for the API are the following:
  • Present a DOM type representation of the BSON data. A BSON Document is presented as a std::map type interface (note a document is not a std::map).
  • Allow easy in-place editing of the elements/fields in the document. This is where the API differs fundamentally from the MongoDB C++ driver API. Once created, a Document instance has no connection to the BSON data from which it was initialised. Similarly, when a BSON representation of the document is generated, it is disconnected from the document instance.
  • Allow serialisation to and from BSON data using streams. This allows initialising a Document instance directly from a file or network socket without needing to first read the BSON data into a vector/char array, and serialising a Document instance directly to a file or network socket without having to keep the entire BSON data representation in memory during the process.
  • Allow serialisation and de-serialisation to and from a custom JSON representation. Note that the JSON representation attempts to stay close to the BSON specification, and is not designed to be Web UI friendly.
  • Present a std::vector type Array implementation. Unlike the MongoDB C++ implementation, an Array is not related to a Document (in inheritance terms) in any way. This preserves the unrelated interfaces presented by std::map and std::vector.
  • Allow serialisation and de-serialisation of Array instances to and from BSON/JSON.
  • Provide a simple callback based ODM (Object-Document mapping) framework for classes that inherit from Object. Object implementations implement a few callback methods which are used for BSON (de)serialisation.
  • A more elegant (no need for ugly if-else loop based on field name) function pointer based ODM has been introduced with release 2.5 and is based on inheriting from ODMObject.
  • Work on a variety of platforms. The library has been tested on Mac OS X, Solaris and Windows.

Document

The following sample code illustrates the features exposed by the API for BSON documents.

#include <uma/bson/Element.h>
#include <uma/bson/Double.h>
#include <uma/bson/String.h>
#include <uma/bson/Document.h>
#include <uma/bson/Array.h>
#include <uma/bson/Undefined.h>
#include <uma/bson/ObjectId.h>
#include <uma/bson/Boolean.h>
#include <uma/bson/Date.h>
#include <uma/bson/Null.h>
#include <uma/bson/RegularExpression.h>
#include <uma/bson/Integer.h>
#include <uma/bson/Timestamp.h>
#include <uma/bson/Long.h>

#include <Poco/FileStream.h>

using uma::bson::Value;
using uma::bson::Element;
using uma::bson::Double;
using uma::bson::String;
using uma::bson::Document;
using uma::bson::Array;
using uma::bson::Undefined;
using uma::bson::ObjectId;
using uma::bson::Boolean;
using uma::bson::Date;
using uma::bson::Null;
using uma::bson::RegularExpression;
using uma::bson::Integer;
using uma::bson::Timestamp;
using uma::bson::Long;

using std::string;

void document()
{
  Document emptyDoc = Document::emptyDocument(); // without auto generated _id field
  emptyDoc["prop1"] = "Property One";
  emptyDoc["prop2"] = "Property Two";

  Array array;
  array << 1.0;
  array[1] = "string value";
  array << Undefined();
  array << true;
  array << date;
  array << Null();
  array << RegularExpression( "^abc", "$(1)" );
  array << 1;
  array << static_cast<int64_t>( 2 );

  Document doc();
  doc.set( "double", 1.0 );
  doc["string"] = "string value" );
  doc.set( "childDoc", emptyDoc );
  doc.set( "childArray", array );
  doc["undefined"]; // defaults to an element with value Undefined
  doc.set( "boolean", true );
  doc.set( "date", date );
  doc["null"] = new Null() );
  doc["regex"] = new RegularExpression( "^abc", "$(1)" ) );
  doc.set( "integer", 1 );
  doc.set( "long", static_cast<int64_t>( 2 ) );

  assert( doc["double"].getType() == Value::Double );
  verify( doc );
}

void bson( const Document& doc )
{
  const std::string out( "/tmp/doctest.bson" );
  Poco::FileOutputStream fos( out, std::ios::out | std::ios::binary | std::ios::trunc );
  doc.toBson( fos );
  fos.close();

  Document d = Document::fromFile( out );
  verify( d );
}

void json( const Document& doc )
{
  const std::string out( "/tmp/doctest.json" );
  Poco::FileOutputStream fos( out, std::ios::out | std::ios::binary | std::ios::trunc );
  doc.toJson( fos, true );
  fos.close();

  Poco::FileInputStream fis( out );
  Document d = Document::fromJson( fis );
  verify( d );
}

void verify( const Document& doc )
{
  assert( doc.get( "double" ).getType() == Value::Double );
  assert( doc.get( "double" ).getValue<Double>().getValue() == 1.0 );

  assert( doc.get( "string" ).getType() == Value::String );
  assert( doc.get( "string" ).getValue<String>().getValue() == "string value" );

  assert( doc.get( "undefined" ).getType() == Value::Undefined );

  assert( doc.get( "boolean" ).getType() == Value::Boolean );
  assert( doc.get( "boolean" ).getValue<Boolean>().getValue() == true );

  assert( doc.get( "date" ).getType() == Value::Date );
  assert( doc.get( "date" ).getValue<Date>() == date );

  assert( doc.get( "null" ).getType() == Value::Null );

  assert( doc.get( "regex" ).getType() == Value::RegEx );
  assert( doc.get( "regex" ).getValue<RegularExpression>().getRegex() == "^abc" );
  assert( doc.get( "regex" ).getValue<RegularExpression>().getFlags() == "$(1)" );

  assert( doc.get( "integer" ).getType() == Value::Type::Integer );
  assert( doc.get( "integer" ).getValue<Integer>().getValue() == 1 );

  assert( doc.get( "long" ).getType() == Value::Type::Long );
  assert( doc.get( "long" ).getValue<Long>().getValue() == 2 );
}


Array

The following sample code illustrates the features exposed by the Array interface.

#include <uma/bson/Element.h>
#include <uma/bson/Double.h>
#include <uma/bson/String.h>
#include <uma/bson/Document.h>
#include <uma/bson/Array.h>
#include <uma/bson/Undefined.h>
#include <uma/bson/ObjectId.h>
#include <uma/bson/Boolean.h>
#include <uma/bson/Date.h>
#include <uma/bson/Null.h>
#include <uma/bson/RegularExpression.h>
#include <uma/bson/Integer.h>
#include <uma/bson/Timestamp.h>
#include <uma/bson/Long.h>

#include <Poco/FileStream.h>

using uma::bson::Value;
using uma::bson::Element;
using uma::bson::Double;
using uma::bson::String;
using uma::bson::Document;
using uma::bson::Array;
using uma::bson::Undefined;
using uma::bson::ObjectId;
using uma::bson::Boolean;
using uma::bson::Date;
using uma::bson::Null;
using uma::bson::RegularExpression;
using uma::bson::Integer;
using uma::bson::Timestamp;
using uma::bson::Long;

using std::string;

void array()
{
  Array array;
  array << 1.0;
  array[1] = "string value";
  array[2]; // default assign an element with value Undefined()
  array << true;
  array << new Date;
  array[5] = new Null;
  array[6] = new RegularExpression( "^abc", "$(1)" );
  array << 1;
  array << static_cast<int64_t>( 2 );
}


void ArrayTest::bson()
{
  const std::string out( "/tmp/arrtest.bson" );
  Poco::FileOutputStream fos( out, std::ios::out | std::ios::binary | std::ios::trunc );
  array.toBson( fos );
  fos.close();

  Array a = Array::fromFile( out );
  verify( a );
}

void json()
{
  const std::string out( "/tmp/arrtest.json" );
  Poco::FileOutputStream fos( out, std::ios::out | std::ios::binary | std::ios::trunc );
  array.toJson( fos, true );
  fos.close();

  Poco::FileInputStream fis( out );
  Array a = Array::fromJson( fis );
  verify( a );
}

void verify( const Array& arr )
{
  assert( arr.size() == 9 );

  assert( arr[0].getType() == Value::Double );
  assert( arr[0].getValue<Double>().getValue() == 1.0 );

  assert( arr[1].getType() == Value::String );
  assert( arr[1].getValue<String>().getValue() == "string value" );

  assert( arr[2].getType() == Value::Undefined );

  assert( arr[3].getType() == Value::Boolean );
  assert( arr[3].getValue<Boolean>().getValue() == true );

  assert( arr[4].getType() == Value::Date );
  assert( arr[5].getType() == Value::Null );

  assert( arr[6].getType() == Value::RegEx );
  assert( arr[6].getValue<RegularExpression>().getRegex() == "^abc" );
  assert( arr[6].getValue<RegularExpression>().getFlags() == "$(1)" );

  assert( arr[7].getType() == Value::Integer );
  assert( arr[7].getValue<Integer>().getValue() == 1 );

  assert( arr[8].getType() == Value::Long );
  assert( arr[8].getValue<Long>().getValue() == 2 );
}


Building

The API has been built and tested on the following platforms:
• Mac OS X Mountain Lion - Xcode 4.5 (LLVM 3.1) 64 bit
• Solaris 11 - GCC 4.7.2 32 & 64 bit, Solaris Studio 12.2/3 32 & 64 bit
Windows 7 - MSVS 2010 32 bit, 2012 64 bit

Scripts are provided under the bin directory in the subversion repository. You may use one of those as a sample to build the library. You can also load the bson.pro (and optionally test.pro) files in Qt Creator on any platform and build the library as appropriate.

#!/bin/ksh

QMAKE=/opt/qt/bin/qmake
BUILD_DIR=`dirname $0`/../build
LIB_DIR=$BUILD_DIR/lib
TEST_DIR=$BUILD_DIR/test
QMAKE_SPEC=solaris-cc-64-stlport
DEFINES="CONFIG+=debug CONFIG+=declarative_debug"
MAKE=/usr/bin/gmake

if [ ! -d $LIB_DIR ]; then mkdir -p $LIB_DIR; fi
if [ ! -d $TEST_DIR ]; then mkdir -p $TEST_DIR; fi

(cd $LIB_DIR; $QMAKE -spec $QMAKE_SPEC $DEFINES ../../bson.pro; $MAKE -j8)

if [ $? -eq 0 ]
then
  (cd $TEST_DIR
    $QMAKE -spec $QMAKE_SPEC $DEFINES ../../test.pro
    $MAKE -j8
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/poco/lib:../lib
    ./UnitTest
  )
fi