...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
/* This example parses a JSON file and pretty-prints it to standard output. */ #include <boost/json.hpp> #include <iomanip> #include <iostream> #include "file.hpp" namespace json = boost::json; json::value parse_file( char const* filename ) { file f( filename, "r" ); json::stream_parser p; boost::system::error_code ec; do { char buf[4096]; auto const nread = f.read( buf, sizeof(buf) ); p.write( buf, nread, ec ); } while( ! f.eof() ); if( ec ) return nullptr; p.finish( ec ); if( ec ) return nullptr; return p.release(); } void pretty_print( std::ostream& os, json::value const& jv, std::string* indent = nullptr ) { std::string indent_; if(! indent) indent = &indent_; switch(jv.kind()) { case json::kind::object: { os << "{\n"; indent->append(4, ' '); auto const& obj = jv.get_object(); if(! obj.empty()) { auto it = obj.begin(); for(;;) { os << *indent << json::serialize(it->key()) << " : "; pretty_print(os, it->value(), indent); if(++it == obj.end()) break; os << ",\n"; } } os << "\n"; indent->resize(indent->size() - 4); os << *indent << "}"; break; } case json::kind::array: { os << "[\n"; indent->append(4, ' '); auto const& arr = jv.get_array(); if(! arr.empty()) { auto it = arr.begin(); for(;;) { os << *indent; pretty_print( os, *it, indent); if(++it == arr.end()) break; os << ",\n"; } } os << "\n"; indent->resize(indent->size() - 4); os << *indent << "]"; break; } case json::kind::string: { os << json::serialize(jv.get_string()); break; } case json::kind::uint64: case json::kind::int64: case json::kind::double_: os << jv; break; case json::kind::bool_: if(jv.get_bool()) os << "true"; else os << "false"; break; case json::kind::null: os << "null"; break; } if(indent->empty()) os << "\n"; } int main(int argc, char** argv) { if(argc != 2) { std::cerr << "Usage: pretty <filename>" << std::endl; return EXIT_FAILURE; } try { // Parse the file as JSON auto const jv = parse_file( argv[1] ); // Now pretty-print the value pretty_print(std::cout, jv); } catch(std::exception const& e) { std::cerr << "Caught exception: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
/* This example verifies that a file contains valid JSON. */ #include <boost/json.hpp> // This file must be manually included when // using basic_parser to implement a parser. #include <boost/json/basic_parser_impl.hpp> #include <iomanip> #include <iostream> #include "file.hpp" using namespace boost::json; // The null parser discards all the data class null_parser { struct handler { constexpr static std::size_t max_object_size = std::size_t(-1); constexpr static std::size_t max_array_size = std::size_t(-1); constexpr static std::size_t max_key_size = std::size_t(-1); constexpr static std::size_t max_string_size = std::size_t(-1); bool on_document_begin( boost::system::error_code& ) { return true; } bool on_document_end( boost::system::error_code& ) { return true; } bool on_object_begin( boost::system::error_code& ) { return true; } bool on_object_end( std::size_t, boost::system::error_code& ) { return true; } bool on_array_begin( boost::system::error_code& ) { return true; } bool on_array_end( std::size_t, boost::system::error_code& ) { return true; } bool on_key_part( string_view, std::size_t, boost::system::error_code& ) { return true; } bool on_key( string_view, std::size_t, boost::system::error_code& ) { return true; } bool on_string_part( string_view, std::size_t, boost::system::error_code& ) { return true; } bool on_string( string_view, std::size_t, boost::system::error_code& ) { return true; } bool on_number_part( string_view, boost::system::error_code& ) { return true; } bool on_int64( std::int64_t, string_view, boost::system::error_code& ) { return true; } bool on_uint64( std::uint64_t, string_view, boost::system::error_code& ) { return true; } bool on_double( double, string_view, boost::system::error_code& ) { return true; } bool on_bool( bool, boost::system::error_code& ) { return true; } bool on_null( boost::system::error_code& ) { return true; } bool on_comment_part(string_view, boost::system::error_code&) { return true; } bool on_comment(string_view, boost::system::error_code&) { return true; } }; basic_parser<handler> p_; public: null_parser() : p_(parse_options()) { } ~null_parser() { } std::size_t write( char const* data, std::size_t size, boost::system::error_code& ec) { auto const n = p_.write_some( false, data, size, ec ); if(! ec && n < size) ec = error::extra_data; return n; } }; bool validate( string_view s ) { // Parse with the null parser and return false on error null_parser p; boost::system::error_code ec; p.write( s.data(), s.size(), ec ); if( ec ) return false; // The string is valid JSON. return true; } int main(int argc, char** argv) { if(argc != 2) { std::cerr << "Usage: validate <filename>" << std::endl; return EXIT_FAILURE; } try { // Read the file into a string auto const s = read_file( argv[1] ); // See if the string is valid JSON auto const valid = validate( s ); // Print the result if( valid ) std::cout << argv[1] << " contains a valid JSON\n"; else std::cout << argv[1] << " does not contain a valid JSON\n"; } catch(std::exception const& e) { std::cerr << "Caught exception: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
/* This example uses a context that stores an allocator to create sequences during conversions */ #include <boost/container/pmr/monotonic_buffer_resource.hpp> #include <boost/container/pmr/vector.hpp> #include <boost/json.hpp> #include <boost/system/errc.hpp> using namespace boost::json; using namespace boost::container; template< class Alloc > struct use_allocator_t { Alloc allocator; }; template< class Alloc > use_allocator_t< Alloc > use_allocator( Alloc alloc ) noexcept { return { alloc }; } template< class T, class Alloc, class FullContext, class = typename std::enable_if< is_sequence_like<T>::value && std::uses_allocator<T, Alloc>::value >::type > boost::system::result<T> tag_invoke( try_value_to_tag<T>, const value& jv, const use_allocator_t<Alloc>& ctx, const FullContext& full_ctx ) { array const* arr = jv.if_array(); if( !arr ) return { boost::system::in_place_error, make_error_code(boost::system::errc::invalid_argument) }; T result(ctx.allocator); auto ins = std::inserter(result, result.end()); for( value const& val: *arr ) { using ValueType = typename T::value_type; auto elem_res = try_value_to<ValueType>( val, full_ctx ); if( elem_res.has_error() ) return {boost::system::in_place_error, elem_res.error()}; *ins++ = std::move(*elem_res); } return result; } int main(int, char**) { value const jv = { 1, 2, 3, 4, 5, 6, 7, 8 }; unsigned char buf[1024]; pmr::monotonic_buffer_resource mr( buf, sizeof(buf) ); auto v = value_to< pmr::vector<int> >( jv, use_allocator( pmr::polymorphic_allocator<int>(&mr) ) ); assert( v.size() == jv.as_array().size() ); for( auto i = 0u; i < v.size(); ++i ) assert( v[i] == jv.at(i).to_number<int>() ); return EXIT_SUCCESS; }
/* This example implements simple parsing and serialization of a subset of CBOR types that are directly supported by JSON. */ #include <boost/json.hpp> #include <boost/endian.hpp> #include <stdexcept> #include <fstream> #include <iostream> using namespace boost::json; void serialize_cbor_number( unsigned char mt, std::uint64_t n, std::vector<unsigned char> & out ) { mt <<= 5; if( n < 24 ) { out.push_back( static_cast<unsigned char>( mt + n ) ); } else if( n < 256 ) { unsigned char data[] = { static_cast<unsigned char>( mt + 24 ), static_cast<unsigned char>( n ) }; out.insert( out.end(), std::begin( data ), std::end( data ) ); } else if( n < 65536 ) { unsigned char data[] = { static_cast<unsigned char>( mt + 25 ), static_cast<unsigned char>( n >> 8 ), static_cast<unsigned char>( n ) }; out.insert( out.end(), std::begin( data ), std::end( data ) ); } else if( n < 0x1000000ull ) { unsigned char data[ 5 ]; data[ 0 ] = static_cast<unsigned char>( mt + 26 ); boost::endian::endian_store<std::uint32_t, 4, boost::endian::order::big>( data + 1, static_cast<std::uint32_t>( n ) ); out.insert( out.end(), std::begin( data ), std::end( data ) ); } else { unsigned char data[ 9 ]; data[ 0 ] = static_cast<unsigned char>( mt + 27 ); boost::endian::endian_store<std::uint64_t, 8, boost::endian::order::big>( data + 1, n ); out.insert( out.end(), std::begin( data ), std::end( data ) ); } } void serialize_cbor_string( string_view sv, std::vector<unsigned char>& out ) { std::size_t n = sv.size(); serialize_cbor_number( 3, n, out ); out.insert( out.end(), sv.data(), sv.data() + n ); } void serialize_cbor_value( const value& jv, std::vector<unsigned char>& out ) { switch( jv.kind() ) { case kind::null: out.push_back( 224 + 22 ); break; case kind::bool_: out.push_back( 224 + 20 + jv.get_bool() ); break; case kind::int64: { std::int64_t n = jv.get_int64(); if( n >= 0 ) serialize_cbor_number( 0, n, out ); else serialize_cbor_number( 1, ~n, out ); } break; case kind::uint64: serialize_cbor_number( 0, jv.get_uint64(), out ); break; case kind::double_: { unsigned char data[ 9 ]; data[ 0 ] = 224 + 27; boost::endian::endian_store<double, 8, boost::endian::order::big>( data + 1, jv.get_double() ); out.insert( out.end(), std::begin(data), std::end(data) ); } break; case kind::string: serialize_cbor_string( jv.get_string(), out ); break; case kind::array: { const array& ja = jv.get_array(); std::size_t n = ja.size(); out.reserve( out.size() + n + 1 ); serialize_cbor_number( 4, n, out ); for( std::size_t i = 0; i < n; ++i ) serialize_cbor_value( ja[i], out ); } break; case kind::object: { const object& jo = jv.get_object(); std::size_t n = jo.size(); out.reserve( out.size() + 3 * n + 1 ); serialize_cbor_number( 5, n, out ); for( const key_value_pair& kv: jo ) { serialize_cbor_string( kv.key(), out ); serialize_cbor_value( kv.value(), out ); } } break; } } BOOST_NORETURN void throw_eof_error() { throw std::runtime_error( "Unexpected end of input" ); } BOOST_NORETURN void throw_format_error( char const * err ) { throw std::runtime_error( err ); } void ensure( std::size_t n, const unsigned char* first, const unsigned char* last ) { if( static_cast<std::size_t>(last - first) < n ) throw_eof_error(); } const unsigned char* parse_cbor_value( const unsigned char* first, const unsigned char* last, value& v ); const unsigned char* parse_cbor_number( const unsigned char* first, const unsigned char* last, unsigned char ch, std::uint64_t& n ) { unsigned char cv = ch & 31; if( cv < 24 ) { n = cv; } else if( cv == 24 ) { ensure( 1, first, last ); n = *first++; } else if( cv == 25 ) { ensure( 2, first, last ); n = boost::endian::load_big_u16( first ); first += 2; } else if( cv == 26 ) { ensure( 4, first, last ); n = boost::endian::load_big_u32( first ); first += 4; } else if( cv == 27 ) { ensure( 8, first, last ); n = boost::endian::load_big_u64( first ); first += 8; } else if( cv == 31 ) { // infinite array/object throw_format_error( "Infinite sequences aren't supported" ); } else { throw_format_error( "Invalid minor type" ); } return first; } const unsigned char* parse_cbor_string( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { std::uint64_t n; first = parse_cbor_number( first, last, ch, n ); ensure( n, first, last ); string_view sv( reinterpret_cast<char const*>( first ), n ); first += n; v = sv; return first; } const unsigned char* parse_cbor_array( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { std::uint64_t n; first = parse_cbor_number( first, last, ch, n ); array & a = v.emplace_array(); a.resize( n ); std::size_t i = 0; for( ; i < n; ++i ) // double[] fast path { ensure( 1, first, last ); unsigned char ch2 = *first; if( ch2 != 0xFB ) break; ++first; ensure( 8, first, last ); double w = boost::endian::endian_load<double, 8, boost::endian::order::big>( first ); first += 8; a[ i ] = w; } for( ; i < n; ++i ) // int[] fast path { ensure( 1, first, last ); unsigned char ch2 = *first; if( ch2 >= 0x40 ) break; ++first; std::uint64_t m; first = parse_cbor_number( first, last, ch2, m ); if( ch2 < 0x20 ) { a[ i ] = m; } else { a[ i ] = static_cast<std::int64_t>( ~m ); } } for( ; i < n; ++i ) first = parse_cbor_value( first, last, a[ i ] ); return first; } const unsigned char* parse_cbor_object( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { std::uint64_t n; first = parse_cbor_number( first, last, ch, n ); object & o = v.emplace_object(); o.reserve( n ); for( std::size_t i = 0; i < n; ++i ) { // key string ensure( 1, first, last ); unsigned char ch2 = *first++; if( ( ch2 >> 5 ) != 3 ) throw_format_error( "Object keys must be strings" ); std::uint64_t m; first = parse_cbor_number( first, last, ch2, m ); ensure( m, first, last ); string_view sv( reinterpret_cast<char const*>( first ), m ); first += m; // value first = parse_cbor_value( first, last, o[ sv ] ); } return first; } const unsigned char* parse_cbor_unsigned( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { std::uint64_t n; first = parse_cbor_number( first, last, ch, n ); v = n; return first; } const unsigned char* parse_cbor_signed( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { std::uint64_t n; first = parse_cbor_number( first, last, ch, n ); v = static_cast<std::int64_t>( ~n ); return first; } const unsigned char* parse_cbor_semantic_tag( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { std::uint64_t n; first = parse_cbor_number( first, last, ch, n ); // ignore semantic tags return parse_cbor_value( first, last, v ); } const unsigned char* parse_cbor_type7( const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) { switch( ch & 31 ) { case 20: v = false; return first; case 21: v = true; return first; case 22: v = nullptr; return first; case 26: // float { ensure( 4, first, last ); float w = boost::endian::endian_load<float, 4, boost::endian::order::big>( first ); first += 4; v = w; return first; } case 27: // double { ensure( 8, first, last ); double w = boost::endian::endian_load<double, 8, boost::endian::order::big>( first ); first += 8; v = w; return first; } default: throw_format_error( "Invalid minor type for major type 7" ); } } const unsigned char* parse_cbor_value( const unsigned char* first, const unsigned char* last, value& v ) { ensure( 1, first, last ); const unsigned char ch = *first++; switch( ch >> 5 ) { case 0: return parse_cbor_unsigned( first, last, ch, v ); case 1: return parse_cbor_signed( first, last, ch, v ); case 2: throw_format_error( "Binary strings aren't supported" ); case 3: return parse_cbor_string( first, last, ch, v ); case 4: return parse_cbor_array( first, last, ch, v ); case 5: return parse_cbor_object( first, last, ch, v ); case 6: return parse_cbor_semantic_tag( first, last, ch, v ); case 7: return parse_cbor_type7( first, last, ch, v ); default: BOOST_JSON_UNREACHABLE(); } } int main(int argc, const char** argv) { if( argc != 2 ) { std::cerr << "Usage: cbor FILE_NAME\n"; return EXIT_FAILURE; } std::ifstream is(argv[1]); is.exceptions(std::ios::badbit); const value jv = parse(is); std::vector<unsigned char> out; serialize_cbor_value( jv, out ); value jv2; parse_cbor_value( out.data(), out.data() + out.size(), jv2 ); if( jv != jv2 ) { std::cerr << "Roundtrip check failed\n"; return EXIT_FAILURE; } return EXIT_SUCCESS; }