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 (https://wanderinghorse.net/home/stephan/)
Requirements:
- Modern C++ compiler with good support for templates. "Modern" really means C++98-compatible.
- The C++ 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.
- UTF-8 (uses the amazing utfcpp toolkit for its UTF conversions).
- 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 (formerly of s11n.net, before that site was bought by an SEO organization), which serializes near-arbitrary native objects to/from JSON.
- This code has been known to run on Windows but it is neither developed, maintained, nor regularly tested there. While the build tree is Linux-centric, the code uses only ISO-specified C++ APIs, and ANSI-specified APIs for the C bits, so it should work on any compliant platform once the user drops it into their build environment.
Primary Misfeatures
- 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 wherever possible, but there is still room for optimization here. Some of the more interesting optimizations are hindered by earlier design decisions which need to be worked out.
- 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!") ); // Output to human-formatted JSON: JsonFormatter fmt; fmt.format( root, std::cout ); // also accepts an output iterator