![]() |
General Programming »
Algorithms & Recipes »
Parsers
Intermediate
License: The MIT License
JSON Spirit: A C++ JSON Parser/Generator Implemented with Boost SpiritBy John W. WilkinsonA C++ JSON parser/generator written using Boost::spirit |
C++, Windows, Visual-Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
JSON is a text file format similar to XML, but less verbose. It has been called "XML lite". This article describes JSON Spirit, a C++ library that reads and writes JSON files or streams. It is written using the Boost Spirit parser generator. If you are already using Boost, you can use JSON Spirit without any additional dependencies.
Key features:
std::vector or std::map implementations for JSON Objects The JSON Spirit source code is available as a Microsoft Visual Studio 2005 C++ "solution". However, it should compile and work on any platform compatible with Boost. JSON Spirit has been built and tested with Visual C++ 2005, 2008, 2010 (beta) and G++ version 4.2.3 on Linux. It has been tested with Visual C++ using Boost versions 1.34.0, 1.37.0, 1.39.0 and 1.41.0. It has also been tested with STLPort.
Platform independent CMake files are included, kindly supplied by Uwe Arzt, with CPack declarations for packaging and a "make install" target supplied by Amzlkej.
The Visual C++ solution consists of five projects:
There are two ways to use the library:
Linking to the object file has the advantage that the heavily templated code only has to be compiled once. Compiling the code can take some time. If you just include the header file implementation, the code will be compiled each time it is included. The use of precompiled headers should help but I have not investigated this.
The include files needed to read JSON text using the object library are json_spirit_reader.h and json_spirit_value.h. To generate JSON text, you need json_spirit_writer.h and json_spirit_value.h. Alternately, you can include json_spirit.h, which includes all three of the above.
The include files needed when using the header only implementation are json_spirit_reader_template.h and json_spirit_writer_template.h.
All JSON Spirit declarations are in the namespace json_spirit.
You can read JSON data from a stream or a string:
bool read( const std::string& s, Value& value );
bool read( std::istream& is, Value& value );
For example:
ifstream is( "json.txt" );
Value value;
read( is, value );
You can also read JSON data by supplying a pair of string iterators.
bool read( std::string::const_iterator& begin,
std::string::const_iterator end, Value& value );
After a successful read, the iterator "begin" will point one past the last character of the text for the value just read. This allows the decoding of a string containing multiple top level values. A subsequent call to read will read the next value in the string.
Similarly the stream reading functions now allow a sequence of top-level values to be read one at a time. Before version 3.0, a stream was converted to a string before being parsed. This was fine for files, but not if for example you want to read multiple JSON values from a socket.
A JSON value can hold either a JSON array, JSON object, string, integer, double, bool , or null. The interface of the JSON Spirit Value class is shown below. The Value class for Unicode is analogous; for details, see the section on Unicode support.
enum Value_type{ obj_type, array_type, str_type,
bool_type, int_type, real_type, null_type };
class Value
{
public:
Value(); // creates null value
Value( const char* value );
Value( const std::string& value );
Value( const Object& value );
Value( const Array& value );
Value( bool value );
Value( int value );
Value_impl( boost::int64_t value );
Value_impl( boost::uint64_t value );
Value( double value );
bool operator==( const Value& lhs ) const;
Value_type type() const;
const std::string& get_str() const;
const Object& get_obj() const;
const Array& get_array() const;
bool get_bool() const;
int get_int() const;
boost::int64_t get_int64() const;
boost::uint64_t get_uint64() const;
double get_real() const;
Object& get_obj();
Array& get_array();
template< typename > T get_value() const;
bool is_uint64() const;
bool is_null() const;
static const Value null;
private:
...
};
You obtain the Value's type by calling Value::type(). You can then call the appropriate getter function. Generally, you will know a file's format, so you will know what type the JSON values should have. A std::runtime_error exception is thrown if you try to get a value of the wrong type, for example if you try to extract a string from a value containing an integer.
The template getter function get_value() is an alternative to get_int(), get_real(), etc. Example usage would be:
int i = value_1.get_value< int >();
double d = value_2.get_value< double >();
A top level Value read from a file or stream normally contains an Array or an Object. An Array is a std::vector of values. An Object is, by default, a std::vector of JSON pairs.
typedef std::vector< Pair > Object;
typedef std::vector< Value > Array;
A Pair is a structure that holds a std::string and a Value.
struct Pair
{
Pair( const std::string& name, const Value& value );
bool operator==( const Pair& lhs ) const;
std::string name_;
Value value_;
};
JSON Arrays and Objects can themselves contain other Arrays or Objects, forming a tree.
Note, JSON Spirit provides an alternative std::map based Object, see the Map Implementation section below. In this case, a Pair type is not needed as an mOject is std::map of names to values.
typedef std::map< std::string, mValue > mObject;
To output JSON, you first create a Value object containing your data, then, write the created Value to a stream or string. There are two versions of each function: one outputs the JSON data without any white-space, the other formats the data by adding white-space and line breaks.
void write ( const Value& value, std::ostream& os );
void write_formatted( const Value& value, std::ostream& os );
std::string write ( const Value& value );
std::string write_formatted( const Value& value );
The following example shows how to create a small JSON file containing an object with three members:
Object addr_obj;
addr_obj.push_back( Pair( "house_number", 42 ) );
addr_obj.push_back( Pair( "road", "East Street" ) );
addr_obj.push_back( Pair( "town", "Newtown" ) );
ofstream os( "address.txt" );
write_formatted( addr_obj, os );
os.close();
The object addr_obj is automatically converted into a Value as it is passed to write_formatted. The file address.txt will contain:
{
"house_number" : 42,
"road" : "East Street",
"town" : "Newtown"
}
Unicode support is provided by std::wstring versions of the JSON Spirit Value, Array, Object, and Pair types. These are called wValue, wArray, wObject, and wPair. There are also std::wstring versions of each reader and writer function.
Note that there is no support for reading Unicode files and converting them to wstrings as this is not a task specific to JSON.
The Value and wValue classes are actually instantiations of the template class Value_impl.
Before version 4.00, the JSON Spirit Object type was a std::vector of name/value Pairs. You now have the option of using mObject which is a name/value std::map. For the std::map version, use mValue instead of Value, mObject instead of Object and mArray instead of Array. For the Unicode map version, use wmValue, wmObject and wmArray.
The following table shows the times in seconds it takes on my PC to extract the data from a single object of varying sizes. The methods used are as per the demo programs. The vector version is faster until the number of object members reached around 10, but then gets exponentially slower.
| size | vector | map |
| 2 | 2.03253e-007 | 3.24672e-007 |
| 5 | 7.63788e-007 | 9.59516e-007 |
| 10 | 2.26948e-006 | 2.00929e-006 |
| 15 | 4.68965e-006 | 3.28509e-006 |
| 20 | 8.19667e-006 | 4.75871e-006 |
| 30 | 1.72695e-005 | 7.64189e-006 |
| 50 | 4.63171e-005 | 1.39316e-005 |
| 75 | 0.000102418 | 2.27205e-005 |
| 100 | 0.000179923 | 3.17732e-005 |
| 200 | 0.000709335 | 7.3061e-005 |
| 500 | 0.00440368 | 0.00022076 |
| 1000 | 0.0175385 | 0.000495554 |
| 10000 | 1.76533 | 0.00658268 |
| 100000 | 178.718 | 0.127325 |
| 1000000 | 17800.718 | 1.99467 |
Note with a vector, object members will be written out in the same order they were read in. A map will sort members alphabetically. A vector object also allows members to have duplicate names. This might be useful in some circumstances but would be non-standard.
From version 3.00, JSON Spirit provides functions that report the position of format errors in text being parsed. These functions are identical to the normal read functions except that instead of returning false if an error is found, they throw a json_spirit::Error_position exception. Note, these functions run about three times slower than the normal read functions.
The Error_position structure holds the line and column number where the first error was found.
struct Error_position
{
...
unsigned int line_;
unsigned int column_;
std::string reason_;
};
If you intend to use JSON Spirit in more than one thread, you will need to uncomment the following line near the top of json_spirit_reader.cpp.
//#define BOOST_SPIRIT_THREADSAFE
In this case, Boost Spirit will require you to link against Boost Threads.
There is a Stream_reader class to work around a bug in boost spirit::multi_pass. It allows multiple top level values to be read from a stream that are not separated by white space. The standard stream reading functions will fail in this case.
istringstream is( "[1][1,2][1,2,3]" );
Stream_reader< istringstream, Value > reader( is );
Value value;
const bool ok = reader.read_next( value ); // read first array
reader.read_next( value ); // read second array
reader.read_next( value ); // read third array
c_ecscape_ch_p, simplifying the code const char* Arrays and Objects, see Daniel Friederich's message thread below [ 1 2 ]" is read as "[ 1, 2 ]" get_value() #define BOOST_SPIRIT_THREADSAFE string or stream string iterators obj_to_map, map_to_obj, find_value std::map implementation for objects 3 to be read as integers or floating point numbers switch statement Value_impl class to use boost variant, giving a speed improvement Stream_reader class to work around a bug in boost spirit::multi_pass CPack declarations to CMake file std:runtime_error is now thrown instead of an assert firing on an attempt to get a value of the wrong type CMake minimum boost version is 1.34, not 1.37
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 20 Jan 2010 Editor: Deeksha Shenoy |
Copyright 2007 by John W. Wilkinson Everything else Copyright © CodeProject, 1999-2010 Web17 | Advertise on the Code Project |