nosjob  nosjob

nosjob: a C++ library for working with JSON data

Welcome to nosjob, a C++ library for generating and consuming JSON data.

The name "nosjob" (pronounced "nose job") came about one early morning after about 12 hours of hacking. My tired eyes looked on the words "JSON object" and briefly read them as "NOSJ ob..." And so the name was chosen. (There are probably already ten other JSON/C++ toolkits with the name "jsonpp," which was my initial choice of names.)

License: the core code is released into the Public Domain, but the stuff under src/utf8/ and src/parser/ were written by other people and have BSD-like licenses ("do whatever you want with the code, but don't remove the copyright notice").

Author: Stephan Beal (http://wanderinghorse.net/home/stephan/)

Requirements:

  • Modern C++ compiler with good support for templates.
  • The STL.

A small amount of 3rd-party code is also required, but is included in this source tree.

Primary Features

  • Easy to generate JSON-compatible data using JSON-like objects from C++ code.
  • Easy parsing of JSON string data (from arbitrary input iterators) into C++ objects.
  • Fairly well documented. (i'm an API docs fanatic.)
  • Supports JSON-compliant numeric, Boolean, Null, String, Object, and Array data. Strings can be UTF-8 or 16-bit UTF-16 (the full range of UTF-16 code points is not supported... at least i don't think so).
  • Compiles cleanly on 64-bit and 32-bit platforms.
  • It can handle ASCII or UTF-8, or UTF-16 data. (That said, UTF-16 is only very lightly tested!)
  • Uses the amazing utfcpp toolkit for its UTF conversions.
  • Uses Jean Gressman's slick JSON parser to parse JSON input.
  • Supports formatted and unformatted JSON output (that is, with or without extra spacing for human-readability).
  • The data types have full value semantics, and can be copied around cheaply while still internally retaining all type-specific information. This functionality does not use virtual inheritance.
  • Includes a client-extendable template-based mechanism for converting between the library's data types and native types. e.g. convert from a std::map to an Object or a std::list to an Array, provided the key/value types in the map/list are themselves convertible.
  • Includes a miniature version of libs11n, which serializes near-arbitrary native objects to/from JSON.
  • The internally-used page-based allocator has optional support for pthread- or Win32-based mutex locking. Which reminds me...
  • This code is untested on any non-Linux platforms. "It should work," but i've never tried. It uses only ISO-specified C++ APIs, and ANSI-specified APIs for the C bits, so it should work on any compliant platform.

Primary Misfeatures

  • Young and experimental. (But what's there seems to work as advertized.)
  • On 32-bit platforms its Integer type currently only supports 32-bit values. This is on the to-fix list (it's easy enough to do, but i hate the extra allocations). On platforms where sizeof(void*)>=sizeof(int64_t), 64-bit integers are used by default.
  • The internals of the API are somewhat convoluted as a side-effect of the polymorphic-copying-without-virtual-inheritance mechanism (see AtomInternals).
  • It models only the data conventions specified by JSON, and not the full range of JavaScript features these types have access to in JS code.
  • It has to allocate memory quite often (but in small amounts). We use reference counting whereever possible, and a page-based memory pool to try to alleviate memory fragementation, but there is still more work to do here. Some of the more interesting optimizations are hindered by earlier design decisions which need to be worked out. That said, it normally requires relatively little memory at any one time. The test/demo application currently (20101117) has a peak concurrent memory usage of only about 20 kilobytes on 64-bit machines and 16kb on 32-bit. (Large JSON trees will of course require more memory.)
  • A few relatively minor, but nice-to-have, features are missing.

Examples

Reading from JSON input:

Atom root = JSONParser().parse( std::cin ); // throws on error
typedef Utf8String STR;
if( Object::isObject(root) ) {
    Object obj = Object::cast(root);
    //... fetch data from the object like so ...
    Atom a = obj.get(STR("myKey"));
    if( ! a.hasValue() ) {
        ... this means it has the special value 'null' or 'undefined' ...
    }
    else if( Integer::isInteger(a) ) {
        Integer::ValueType myInt = Integer::cast(a).value();
        ...
    }
    // Or:
    Object::Iterator it = obj.begin();
    Object::Iterator end = obj.end();
    for( ; it != end; ++it ) {
        Object::Entry const & e = *it;
        std::cout << e.key // UTF-8 string
                  << '='
                  << atomToJSON(e.value)
                  <<'\n';
    }
}
else { // it is an Array...
    Array ar = Array::cast(root);
    ... fetch data from the array, similar to the Object API ...
}

The various "cast" operators above don't really cast anything. They simply copy a couple internal pointers around to upgrade a base Atom object to its full-fledged concrete type. Trying to "cast" to an incompatible type will cause an exception to be thrown.

Creating JSON output (to iterators or streams):

typedef Utf8String STR;
Object root;
root.set(STR("int"),Integer(1));
root.set(STR("double"),Double(2.2));
root.set(STR("bool"),Boolean::True);
Object sub;
root.set(STR("sub"),sub);
sub.set(....);
Array ar;
sub.set(STR("array"),ar);
ar.set( 0, Integer(42) );
ar.set( 1, STR("Hi, world!") );
ar.set( 2, Utf16String("... something UTF-16...") );
// Output to human-formatted JSON:
JsonFormatter fmt;
fmt.format( root, std::cout ); // also accepts an output iterator