Click here to Skip to main content
15,881,709 members
Articles / Web Development / HTML

JSON Spirit: A C++ JSON Parser/Generator Implemented with Boost Spirit

Rate me:
Please Sign up or sign in to vote.
4.92/5 (110 votes)
10 May 2014MIT12 min read 4.1M   24.3K   287  
A C++ JSON parser/generator written using Boost::spirit
/* Copyright (c) 2007-2009 John W Wilkinson

   This source code can be used for any purpose as long as
   this comment is retained. */

// json spirit version 3.00

#include "json_spirit_reader_test.h"
#include "json_spirit_reader.h"
#include "json_spirit_value.h" 
#include "json_spirit_writer.h" 
#include "utils_test.h"

#include <sstream>
#include <boost/assign/list_of.hpp>
#include <boost/timer.hpp>
#include <boost/lexical_cast.hpp>

using namespace json_spirit;
using namespace std;
using namespace boost;
using namespace boost::assign;

namespace
{
    template< class String_t, class Value_t >
    void test_read( const String_t& s, Value_t& value )
    {
        // performs both types of read and checks they produce the same value

        read( s, value );

        Value_t value_2;

        read_or_throw( s, value_2 );

        assert_eq( value, value_2 );
    }

    template< class Value_t >
    struct Test_runner
    {
        typedef typename Value_t::String_type      String_t;
        typedef typename Value_t::Object           Object_t;
        typedef typename Value_t::Array            Array_t;
        typedef typename String_t::value_type      Char_t;
        typedef typename String_t::const_iterator  Iter_t;
        typedef Pair_impl< String_t >              Pair_t;
        typedef std::basic_istringstream< Char_t > Istringstream_t;
        typedef std::basic_istream< Char_t >       Istream_t;

        String_t to_str( const char* c_str )
        {
            return ::to_str< String_t >( c_str );
        }

        Pair_t make_pair( const char* c_name, const char* c_value )
        {
            return Pair_t( to_str( c_name ), to_str( c_value ) );
        }

        Pair_t p1;
        Pair_t p2;
        Pair_t p3;

        Test_runner()
        :   p1( make_pair("name 1", "value 1") )
        ,   p2( make_pair("name 2", "value 2") )
        ,   p3( make_pair("name 3", "value 3") )
        {
        }

        void check_eq( const Object_t& obj_1, const Object_t& obj_2 )
        {
            const typename Object_t::size_type size( obj_1.size() );

            assert_eq( size, obj_2.size() );

            for( typename Object_t::size_type i = 0; i < size; ++i )
            {
                assert_eq( obj_1[i], obj_2[i] ); 
            }
        }

        void test_syntax( const char* c_str, bool expected_success = true )
        {
            const String_t str = to_str( c_str );

            Value_t value;

            const bool ok = read( str, value );

            assert_eq( ok, expected_success  );

            try
            {
                read_or_throw( str, value );

                assert( expected_success );
            }
            catch( ... )
            {
                assert( !expected_success );
            }
        }

        template< typename Int >
        void test_syntax( Int min_int, Int max_int )
        {
            ostringstream os;

            os << "[" << min_int << "," << max_int << "]";

            test_syntax( os.str().c_str() );
        }

        void test_syntax()
        {
            test_syntax( "{}" );
            test_syntax( "{ }" );
            test_syntax( "{ } " );
            test_syntax( "{ }  " );
            test_syntax( "{\"\":\"\"}" );
            test_syntax( "{\"test\":\"123\"}" );
            test_syntax( "{\"test\" : \"123\"}" );
            test_syntax( "{\"testing testing testing\":\"123\"}" );
            test_syntax( "{\"\":\"abc\"}" );
            test_syntax( "{\"abc\":\"\"}" );
            test_syntax( "{\"\":\"\"}" );
            test_syntax( "{\"test\":true}" );
            test_syntax( "{\"test\":false}" );
            test_syntax( "{\"test\":null}" );
            test_syntax( "{\"test1\":\"123\",\"test2\":\"456\"}" );
            test_syntax( "{\"test1\":\"123\",\"test2\":\"456\",\"test3\":\"789\"}" );
            test_syntax( "{\"test1\":{\"test2\":\"123\",\"test3\":\"456\"}}" );
            test_syntax( "{\"test1\":{\"test2\":{\"test3\":\"456\"}}}" );
            test_syntax( "{\"test1\":[\"a\",\"bb\",\"cc\"]}" );
            test_syntax( "{\"test1\":[true,false,null]}" );
            test_syntax( "{\"test1\":[true,\"abc\",{\"a\":\"b\"},{\"d\":false},null]}" );
            test_syntax( "{\"test1\":[1,2,-3]}" );
            test_syntax( "{\"test1\":[1.1,2e4,-1.234e-34]}" );
            test_syntax( "{\n"
                          "\t\"test1\":\n"
                          "\t\t{\n"
                          "\t\t\t\"test2\":\"123\",\n"
                          "\t\t\t\"test3\":\"456\"\n"
                          "\t\t}\n"
                          "}\n" );
            test_syntax( "[]" );
            test_syntax( "[ ]" );
            test_syntax( "[1,2,3]" );
            test_syntax( "[ 1, -2, 3]" );
            test_syntax( "[ 1.2, -2e6, -3e-6 ]" );
            test_syntax( "[ 1.2, \"str\", -3e-6, { \"field\" : \"data\" } ]" );

            test_syntax( INT_MIN, INT_MAX );
            test_syntax( LLONG_MIN, LLONG_MAX );
            test_syntax( "[1 2 3]", false );
        }

        Value_t read_cstr( const char* c_str )
        {
            Value_t value;

            test_read( to_str( c_str ), value );

            return value;
        }

        void read_cstr( const char* c_str, Value_t& value )
        {
            test_read( to_str( c_str ), value );
        }

        void check_reading( const char* c_str )
        {
            Value_t value;

            String_t in_s( to_str( c_str ) );

            test_read( in_s, value );

            const String_t result = write_formatted( value ); 

            assert_eq( in_s, result );
        }

        template< typename Int >
        void check_reading( Int min_int, Int max_int )
        {
            ostringstream os;

            os << "[\n"
                   "    " << min_int << ",\n"
                   "    " << max_int << "\n"
                   "]";

            check_reading( os.str().c_str() );
        }

        void test_reading()
        {
            check_reading( "{\n}" );

            Value_t value;

            read_cstr( "{\n"
                       "    \"name 1\" : \"value 1\"\n"
                       "}", value );

            check_eq( value.get_obj(), list_of( p1 ) );

            read_cstr( "{\"name 1\":\"value 1\",\"name 2\":\"value 2\"}", value );

            check_eq( value.get_obj(), list_of( p1 )( p2 ) );

            read_cstr( "{\n"
                       "    \"name 1\" : \"value 1\",\n"
                       "    \"name 2\" : \"value 2\",\n"
                       "    \"name 3\" : \"value 3\"\n"
                       "}", value );

            check_eq( value.get_obj(), list_of( p1 )( p2 )( p3 ) );

            read_cstr( "{\n"
                       "    \"\" : \"value\",\n"
                       "    \"name\" : \"\"\n"
                       "}", value );

            check_eq( value.get_obj(), list_of( make_pair( "", "value" ) )( make_pair( "name", "" ) ) );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : {\n"
                            "        \"name 3\" : \"value 3\",\n"
                            "        \"name_4\" : \"value_4\"\n"
                            "    }\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : {\n"
                            "        \"name 3\" : \"value 3\",\n"
                            "        \"name_4\" : \"value_4\",\n"
                            "        \"name_5\" : {\n"
                            "            \"name_6\" : \"value_6\",\n"
                            "            \"name_7\" : \"value_7\"\n"
                            "        }\n"
                            "    }\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : {\n"
                            "        \"name 3\" : \"value 3\",\n"
                            "        \"name_4\" : {\n"
                            "            \"name_5\" : \"value_5\",\n"
                            "            \"name_6\" : \"value_6\"\n"
                            "        },\n"
                            "        \"name_7\" : \"value_7\"\n"
                            "    }\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : {\n"
                            "        \"name 3\" : \"value 3\",\n"
                            "        \"name_4\" : {\n"
                            "            \"name_5\" : \"value_5\",\n"
                            "            \"name_6\" : \"value_6\"\n"
                            "        },\n"
                            "        \"name_7\" : \"value_7\"\n"
                            "    },\n"
                            "    \"name_8\" : \"value_8\",\n"
                            "    \"name_9\" : {\n"
                            "        \"name_10\" : \"value_10\"\n"
                            "    }\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : {\n"
                            "        \"name 2\" : {\n"
                            "            \"name 3\" : {\n"
                            "                \"name_4\" : {\n"
                            "                    \"name_5\" : \"value\"\n"
                            "                }\n"
                            "            }\n"
                            "        }\n"
                            "    }\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : true,\n"
                            "    \"name 3\" : false,\n"
                            "    \"name_4\" : \"value_4\",\n"
                            "    \"name_5\" : true\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : null,\n"
                            "    \"name 3\" : \"value 3\",\n"
                            "    \"name_4\" : null\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : 123,\n"
                            "    \"name 3\" : \"value 3\",\n"
                            "    \"name_4\" : -567\n"
                            "}" );

            check_reading( "{\n"
                            "    \"name 1\" : \"value 1\",\n"
                            "    \"name 2\" : 1.200000000000000,\n"
                            "    \"name 3\" : \"value 3\",\n"
                            "    \"name_4\" : 1.234567890123456e+125,\n"
                            "    \"name_5\" : -1.234000000000000e-123,\n"
                            "    \"name_6\" : 1.000000000000000e-123,\n"
                            "    \"name_7\" : 1234567890.123456\n"
                            "}" );

            check_reading( "[\n]" );

            check_reading( "[\n"
                           "    1\n"
                           "]" );

            check_reading( "[\n"
                           "    1,\n"
                           "    1.200000000000000,\n"
                           "    \"john]\",\n"
                           "    true,\n"
                           "    false,\n"
                           "    null\n"
                           "]" );

            check_reading( "[\n"
                           "    1,\n"
                           "    [\n"
                           "        2,\n"
                           "        3\n"
                           "    ]\n"
                           "]" );

            check_reading( "[\n"
                           "    1,\n"
                           "    [\n"
                           "        2,\n"
                           "        3\n"
                           "    ],\n"
                           "    [\n"
                           "        4,\n"
                           "        [\n"
                           "            5,\n"
                           "            6,\n"
                           "            7\n"
                           "        ]\n"
                           "    ]\n"
                           "]" );

            check_reading( "[\n"
                           "    {\n"
                           "        \"name\" : \"value\"\n"
                           "    }\n"
                           "]" );

            check_reading( "{\n"
                           "    \"name\" : [\n"
                           "        1\n"
                           "    ]\n"
                           "}" );

            check_reading( "[\n"
                           "    {\n"
                           "        \"name 1\" : \"value\",\n"
                           "        \"name 2\" : [\n"
                           "            1,\n"
                           "            2,\n"
                           "            3\n"
                           "        ]\n"
                           "    }\n"
                           "]" );

            check_reading( "{\n"
                           "    \"name 1\" : [\n"
                           "        1,\n"
                           "        {\n"
                           "            \"name 2\" : \"value 2\"\n"
                           "        }\n"
                           "    ]\n"
                           "}" );

            check_reading( "[\n"
                           "    {\n"
                           "        \"name 1\" : \"value 1\",\n"
                           "        \"name 2\" : [\n"
                           "            1,\n"
                           "            2,\n"
                           "            {\n"
                           "                \"name 3\" : \"value 3\"\n"
                           "            }\n"
                           "        ]\n"
                           "    }\n"
                           "]" );

            check_reading( "{\n"
                           "    \"name 1\" : [\n"
                           "        1,\n"
                           "        {\n"
                           "            \"name 2\" : [\n"
                           "                1,\n"
                           "                2,\n"
                           "                3\n"
                           "            ]\n"
                           "        }\n"
                           "    ]\n"
                           "}" );

            check_reading( INT_MIN, INT_MAX );
            check_reading( LLONG_MIN, LLONG_MAX );
        }

        void test_from_stream( const char* json_str, bool expected_success,
                               const Error_position& expected_error )
        {
            Value_t value;

            String_t in_s( to_str( json_str ) );

            basic_istringstream< Char_t > is( in_s );

            const bool ok = read( is, value );

            assert_eq( ok, expected_success );

            if( ok )
            {
                assert_eq( in_s, write( value ) );
            }

            try
            {
                basic_istringstream< Char_t > is( in_s );

                read_or_throw( is, value );

                assert_eq( expected_success, true );

                assert_eq( in_s, write( value ) );
            }
            catch( const Error_position error )
            {
                assert_eq( error, expected_error );
            }
        }

        void test_from_stream()
        {
            test_from_stream( "[1,2]", true, Error_position() );
            test_from_stream( "\n\n foo", false, Error_position( 3, 2,"not a value"  ) );
        }

        void test_escape_chars( const char* json_str, const char* c_str )
        {
            Value_t value;

            string s( string( "{\"" ) + json_str + "\" : \"" + json_str + "\"} " );

            read_cstr( s.c_str(), value );

            const Pair_t& pair( value.get_obj()[0] );

            assert_eq( pair.name_,  to_str( c_str ) );
            assert_eq( pair.value_, to_str( c_str ) );
        }

        void test_escape_chars()
        {
            test_escape_chars( "\\t", "\t");
            test_escape_chars( "a\\t", "a\t" );
            test_escape_chars( "\\tb", "\tb" );
            test_escape_chars( "a\\tb", "a\tb" );
            test_escape_chars( "a\\tb", "a\tb" );
            test_escape_chars( "a123\\tb", "a123\tb" );
            test_escape_chars( "\\t\\n\\\\", "\t\n\\" );
            test_escape_chars( "\\/\\r\\b\\f\\\"", "/\r\b\f\"" );
            test_escape_chars( "\\h\\j\\k", "" ); // invalid esc chars
            test_escape_chars( "\\x61\\x62\\x63", "abc" );
            test_escape_chars( "a\\x62c", "abc" );
            test_escape_chars( "\\x01\\x02\\x7F", "\x01\x02\x7F" ); // NB x7F is the greatest char spirit will parse
            test_escape_chars( "\\u0061\\u0062\\u0063", "abc" );
        }

        void check_is_null( const char* c_str  )
        {
            assert_eq( read_cstr( c_str ).type(), null_type ); 
        }

        template< typename T >
        void check_value( const char* c_str, const T& expected_value )
        {
            const Value_t v( read_cstr( c_str ) );
            
            assert_eq( v.template get_value< T >(), expected_value ); 
        }

        void test_values()
        {
            check_value( "1",        1 );
            check_value( "1.5",      1.5 );
            check_value( "\"Test\"", to_str( "Test" ) );
            check_value( "true",     true );
            check_value( "false",    false );
            check_is_null( "null" );
        }

        void check_read_fails( const char* c_str, int line, int column, const string& reason )
        {
            Value_t value;

            try
            {
                read_cstr( c_str, value );

                assert( false );
            }
            catch( const Error_position posn )
            {
                assert_eq( posn, Error_position( line, column, reason ) );
            }
        }

        void test_error_cases()
        {
            check_read_fails( "",                       1, 1,  "not a value" );
            check_read_fails( "foo",                    1, 1,  "not a value" );
            check_read_fails( " foo",                   1, 2,  "not a value" );
            check_read_fails( "  foo",                  1, 3,  "not a value" );
            check_read_fails( "\n\n foo",               3, 2,  "not a value" );
            check_read_fails( "!!!",                    1, 1,  "not a value" );
            check_read_fails( "\"bar",                  1, 1,  "not a value" );
            check_read_fails( "bar\"",                  1, 1,  "not a value" );
            check_read_fails( "[1}",                    1, 3,  "not an array" );
            check_read_fails( "[1,2?",                  1, 5,  "not an array" );
            check_read_fails( "[1,2}",                  1, 5,  "not an array" );
            check_read_fails( "[1;2]",                  1, 3,  "not an array" );
            check_read_fails( "[1,\n2,\n3,]",           3, 2,  "not an array" );
            check_read_fails( "{\"name\":\"value\"]",   1, 16, "not an object" );
            check_read_fails( "{\"name\",\"value\"}",   1, 8,  "no colon in pair" );
            check_read_fails( "{name:\"value\"}",       1, 2,  "not an object" );
            check_read_fails( "{\n1:\"value\"}",        2, 1,  "not an object" );
            check_read_fails( "{\n  name\":\"value\"}", 2, 3,  "not an object" );
            check_read_fails( "{\"name\":foo}",         1, 9,  "not a value" );
            check_read_fails( "{\"name\":value\"}",     1, 9,  "not a value" );
        }

        typedef vector< int > Ints;

        bool test_read_range( Iter_t& first, Iter_t last, Value_t& value )
        {
            Iter_t first_ = first;

            const bool ok = read( first, last, value );

            try
            {
                Value_t value_;

                read_or_throw( first_, last, value_ );

                assert_eq( ok, true );
                assert_eq( value, value_ );
            }
            catch( ... )
            {
                assert_eq( ok, false );
            }

            return ok;
        }

        void check_value_sequence( Iter_t first, Iter_t last, const Ints& expected_values, bool all_input_consumed )
        {
            Value_t value;
            
            for( Ints::size_type i = 0; i < expected_values.size(); ++i )
            {
                const bool ok = test_read_range( first, last, value );

                assert_eq( ok, true );

                const bool is_last( i == expected_values.size() - 1 );

                assert_eq( first == last, is_last ? all_input_consumed : false );
            }
  
            const bool ok = test_read_range( first, last, value );

            assert_eq( ok, false );
        }

        void check_value_sequence( Istream_t& is, const Ints& expected_values, bool all_input_consumed )
        {
            Value_t value;
            
            for( Ints::size_type i = 0; i < expected_values.size(); ++i )
            {
                read_or_throw( is, value );

                assert_eq( value.get_int(), expected_values[i] ); 

                const bool is_last( i == expected_values.size() - 1 );

                assert_eq( is.eof(), is_last ? all_input_consumed : false );
            }
                
            try
            {
                read_or_throw( is, value );

                assert( false );
            }
            catch( ... )
            {
            }
             
            assert_eq( is.eof(), true );
        }

        void check_value_sequence( const char* c_str, const Ints& expected_values, bool all_input_consumed )
        {
            const String_t s( to_str( c_str ) );

            check_value_sequence( s.begin(), s.end(), expected_values, all_input_consumed );

            Istringstream_t is( s );

            check_value_sequence( is, expected_values, all_input_consumed );
        }

        void check_array( const Value_t& value, typename Array_t::size_type expected_size )
        {
            assert_eq( value.type(), array_type );

            const Array_t& arr = value.get_array();

            assert_eq( arr.size(), expected_size );

            for( typename Array_t::size_type i = 0; i < expected_size; ++i )
            {
                const Value_t& val = arr[i];

                assert_eq( val.type(), int_type );
                assert_eq( val.get_int(), int( i + 1 ) );
            }
        }

        void check_reading_array( Iter_t& begin, Iter_t end, typename Array_t::size_type expected_size )
        {
            Value_t value;

            test_read_range( begin, end, value );

            check_array( value, expected_size );
        }

        void check_reading_array( Istream_t& is, typename Array_t::size_type expected_size )
        {
            Value_t value;

            read( is, value );

            check_array( value, expected_size );
        }

        void test_sequence_of_values()
        {
            check_value_sequence( "",   Ints(), false );
            check_value_sequence( " ",  Ints(), false );
            check_value_sequence( "  ", Ints(), false );
            check_value_sequence( "     10 ",      list_of( 10 ), false );
            check_value_sequence( "     10 11 ",   list_of( 10 )( 11 ), false );
            check_value_sequence( "     10 11 12", list_of( 10 )( 11 )( 12 ), true);
            check_value_sequence( "10 11 12",      list_of( 10 )( 11 )( 12 ), true);

            // 

            const String_t str( to_str( "[] [ 1 ] [ 1, 2 ]  [ 1, 2, 3 ]" ) );

            Iter_t       begin = str.begin();
            const Iter_t end   = str.end();

            check_reading_array( begin, end, 0 );
            check_reading_array( begin, end, 1 );
            check_reading_array( begin, end, 2 );
            check_reading_array( begin, end, 3 );

            Istringstream_t is( str );

            check_reading_array( is, 0 );
            check_reading_array( is, 1 );
            check_reading_array( is, 2 );
            check_reading_array( is, 3 );

        }

        void run_tests()
        {
            test_syntax();
            test_reading();
            test_from_stream();
            test_escape_chars();
            test_values();
            test_error_cases();
            test_sequence_of_values();
        }
    };

#ifndef BOOST_NO_STD_WSTRING
    void test_wide_esc_u()
    {
        wValue value;

        test_read( L"[\"\\uABCD\"]", value );

        const wstring s( value.get_array()[0].get_str() );

        assert_eq( s.length(), static_cast< wstring::size_type >( 1u ) );
        assert_eq( s[0], 0xABCD );
    }
#endif

    void test_extended_ascii( const string& s )
    {
        Value value;

        test_read( "[\"" + s + "\"]", value );

        assert_eq( value.get_array()[0].get_str(), "����" );
    }

    void test_extended_ascii()
    {
        test_extended_ascii( "\\u00E4\\u00F6\\u00FC\\u00DF" );
        test_extended_ascii( "����" );
    }
}

#include <fstream>

void json_spirit::test_reader()
{
    Test_runner< Value >().run_tests();

#ifndef BOOST_NO_STD_WSTRING
    Test_runner< wValue >().run_tests();
    test_wide_esc_u();
#endif

    test_extended_ascii();

#ifndef _DEBUG
    //ifstream ifs( "test.txt" );

    //string s;

    //getline( ifs, s );

    //timer t;

    //for( int i = 0; i < 2000; ++i )
    //{
    //    Value value;

    //    read( s, value );
    //}

    //cout << t.elapsed() << endl;

//    const string so = write( value );

    //Object obj;

    //for( int i = 0; i < 100000; ++i )
    //{
    //    obj.push_back( Pair( "\x01test\x7F", lexical_cast< string >( i ) ) );
    //}

    //const string s = write( obj );

    //Value value;

    //timer t;

    //read( s, value );

    //cout << t.elapsed() << endl;

    //cout << "obj size " << value.get_obj().size();
#endif
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) Spirent Communications Plc
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions