Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7761788
Add parse_into
pdimov Oct 21, 2022
76c234b
Add example/parse_into_canada.cpp
pdimov Oct 6, 2021
4c628ae
Add null_handler
pdimov Oct 6, 2021
62af0a0
parse_into header cleanup
grisumbras Oct 19, 2022
56fcb09
reusing existing conversion components
grisumbras Oct 21, 2022
ef6e310
parse_into supports described enums
grisumbras Jul 28, 2023
7133184
parse_into supports optionals
grisumbras Jul 28, 2023
71a6521
extend parse_into API, document it
grisumbras Jul 29, 2023
8e6283a
internal docs for parse_into machinery
grisumbras Aug 11, 2023
9565c71
refactor parse_into tuple handler
grisumbras Aug 13, 2023
b540696
error_code is first argument for parse_into nested handlers' functions
grisumbras Aug 12, 2023
98b85ad
parse_into tuple support works on C++11
grisumbras Aug 15, 2023
7b26a34
parse_into supports variants
grisumbras Aug 6, 2023
10ef306
test/parse_into.cpp requires /bigobj on msvc
grisumbras Aug 15, 2023
9859866
parse_into supports std::array
grisumbras Aug 21, 2023
53b99c8
Add example/parse_into_citm_catalog.cpp
pdimov Oct 7, 2021
4f4396e
test parse_into error reporting
grisumbras Aug 23, 2023
aaa4e7f
add tuple size checking for parse_into
grisumbras Aug 27, 2023
c1b7174
add array size checking for parse_into
grisumbras Aug 27, 2023
f5b9709
add struct member count checking for parse_into
grisumbras Aug 27, 2023
a3821ce
increase parse_into coverage
grisumbras Aug 24, 2023
60bf345
remove unnecessary parameters
grisumbras Sep 9, 2023
34284c3
avoid unnecessary allocations for strings
grisumbras Sep 11, 2023
f144f38
document direct parsing
grisumbras Sep 9, 2023
75981e7
parse_into clears sequences before filling them
grisumbras Sep 30, 2023
e19edde
parse_into clears strings before filling them
grisumbras Oct 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions doc/qbk/conversion/direct.qbk
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[/
Copyright (c) 2023 Dmitry Arkhipov ([email protected])

Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

Official repository: https://github.com/boostorg/json
]

[/-----------------------------------------------------------------------------]


[section Direct parsing]

For large inputs parsing into the library's containers followed by conversion
via __value_to__ might be prohibitively expensive. For this cases the library
provides components that allow parsing directly into user-provided objects.

The drawback of this approach is that fully custom type representations are
not supported, only the library-provided conversions are. Also all objects that
should be populated by parsing have to be default constructible types. This
includes not only the top-level object, but e.g. elements of containers,
members of described `struct`s, and alternatives of variants.

That being said, if your types are default-constructible and you don't need
the customisability allowed by __value_to__, then you can get a significant
performance boost with direct parsing.

Direct parsing is performed by the __parse_into__ family of functions. The
library provides overloads that take either __string_view__ or `std::istream`,
and can report errors either via throwing exceptions or setting an error code.

[doc_parse_into_1]

Finally, if you need to combine incremental parsing with direct parsing, you
can resort to __parser_for__. `parser_for<T>` is an instantiation of
__basic_parser__ that parses into an object of type `T`, and is what
__parse_into__ uses under the hood.

[endsect]
6 changes: 5 additions & 1 deletion doc/qbk/conversion/guidelines.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ then providing a custom one, unless the resulting format is undesirable. If
the library deduces the wrong conversion category, you can opt out by
specialising the relevant trait to inherit from `std::false_type`.

If library-provided conversions are suitable for you, you have the option to
use direct conversions. This also puts the requirement of being default
constructible on many of your types.

The next thing to consider is whether your conversions are intended for
internal use, or whether your users are not members of your team. If your users
are external, then they will ultimately determine the conditions in which these
Expand All @@ -37,6 +41,6 @@ of exceptions from conversion of elements.
Finally, it is worth mentioning that due to the ability to provide conversions
to JSON containers without a binary dependency on the library, you don't have
to push such dependency on your users. This is particularly relevant for
libraries for which interoperation with Boost.JSON is only extra functionality.
libraries for which interoperation with Boost.JSON is only ancillary.

[endsect]
1 change: 1 addition & 0 deletions doc/qbk/conversion/overview.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from the standard library.
[include alloc.qbk]
[include context.qbk]
[include forward.qbk]
[include direct.qbk]
[include guidelines.qbk]

[endsect]
2 changes: 2 additions & 0 deletions doc/qbk/main.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
[def __monotonic_resource__ [link json.ref.boost__json__monotonic_resource `monotonic_resource`]]
[def __object__ [link json.ref.boost__json__object `object`]]
[def __parse__ [link json.ref.boost__json__parse `parse`]]
[def __parse_into__ [link json.ref.boost__json__parse_into `parse_into`]]
[def __parser__ [link json.ref.boost__json__parser `parser`]]
[def __parser_for__ [link json.ref.boost__json__parser_for `parser_for`]]
[def __parse_options__ [link json.ref.boost__json__parse_options `parse_options`]]
[def __polymorphic_allocator__ [link json.ref.boost__json__polymorphic_allocator `polymorphic_allocator`]]
[def __result__ [link json.ref.boost__json__result `result`]]
Expand Down
2 changes: 2 additions & 0 deletions doc/qbk/quickref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<member><link linkend="json.ref.boost__json__get_null_resource">get_null_resource</link></member>
<member><link linkend="json.ref.boost__json__make_shared_resource">make_shared_resource</link></member>
<member><link linkend="json.ref.boost__json__parse">parse</link></member>
<member><link linkend="json.ref.boost__json__parse_into">parse_into</link></member>
<member><link linkend="json.ref.boost__json__result_from_errno">result_from_errno</link></member>
<member><link linkend="json.ref.boost__json__serialize">serialize</link></member>
<member><link linkend="json.ref.boost__json__to_string">to_string</link></member>
Expand Down Expand Up @@ -109,6 +110,7 @@
<member><link linkend="json.ref.boost__json__error_code">error_code</link></member>
<member><link linkend="json.ref.boost__json__error_condition">error_condition</link></member>
<member><link linkend="json.ref.boost__json__memory_resource">memory_resource</link></member>
<member><link linkend="json.ref.boost__json__parser_for">parser_for</link></member>
<member><link linkend="json.ref.boost__json__polymorphic_allocator">polymorphic_allocator</link></member>
<member><link linkend="json.ref.boost__json__result">result</link></member>
<member><link linkend="json.ref.boost__json__string_view">string_view</link></member>
Expand Down
23 changes: 23 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

source_group("" FILES
file.hpp
parse_into.cpp
parse_into_canada.cpp
parse_into_citm_catalog.cpp
path.cpp
pretty.cpp
proxy.cpp
Expand All @@ -17,6 +20,26 @@ source_group("" FILES

#

add_executable(parse_into
parse_into.cpp
)
set_property(TARGET parse_into PROPERTY FOLDER "example")
target_link_libraries(parse_into PRIVATE Boost::json)

add_executable(parse_into_canada
parse_into_canada.cpp
)
set_property(TARGET parse_into_canada PROPERTY FOLDER "example")
target_link_libraries(parse_into_canada PRIVATE Boost::json)

add_executable(parse_into_citm_catalog
parse_into_citm_catalog.cpp
)
set_property(TARGET parse_into_citm_catalog PROPERTY FOLDER "example")
target_link_libraries(parse_into_citm_catalog PRIVATE Boost::json)

#

add_executable(path
path.cpp
)
Expand Down
6 changes: 6 additions & 0 deletions example/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ project : requirements <library>/boost/json//boost_json ;

exe path : path.cpp ;

exe parse_into : parse_into.cpp ;

exe parse_into_canada : parse_into_canada.cpp ;

exe parse_into_citm_catalog : parse_into_citm_catalog.cpp ;

exe pretty : pretty.cpp ;

exe proxy : proxy.cpp ;
Expand Down
235 changes: 235 additions & 0 deletions example/parse_into.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
//
// Copyright (c) 2021 Peter Dimov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/json
//

//
// An example that compares the performance of json::parse and
// json::parse_into, using https://github.com/kostya/benchmarks#json
//
// Typical results:
//
// 1.json: 115075437 bytes
// boost::json::parse: 721 ms
// x: -5.00335e-30, y: 5.00428e+30, z: 0.499722: 121 ms
// parse_into coordinates: 398 ms
// x: -5.00335e-30, y: 5.00428e+30, z: 0.499722: 3 ms
// parse_into coordinates2: 326 ms
// x: -5.00335e-30, y: 5.00428e+30, z: 0.499722: 0 ms
//

#include <boost/json.hpp>
#include <iostream>

#if !defined(BOOST_DESCRIBE_CXX14)

#include <boost/config/pragma_message.hpp>

BOOST_PRAGMA_MESSAGE( "This example requires C++14" )

int main() {}

#else

#include <boost/describe.hpp>

#include <chrono>
#include <fstream>
#include <iterator>
#include <map>
#include <vector>

// An std::map<std::string, std::pair<int, bool>> replacement
// We don't need to store the options

struct options
{
using mapped_type = std::pair<int, bool>;
using value_type = std::pair<std::string, mapped_type>;
using iterator = value_type*;

std::pair<iterator, bool>
emplace( value_type const& );

void
emplace( std::string const&, mapped_type const& )
{
}

iterator
begin();

iterator
end();

void
clear()
{ }
};

struct coordinate
{
double x{}, y{}, z{};
std::string name;
options opts;
};

BOOST_DESCRIBE_STRUCT(coordinate, (), (x, y, z, name, opts))

struct coordinates1
{
std::vector<coordinate> coordinates;
std::string info;
};

BOOST_DESCRIBE_STRUCT(coordinates1, (), (coordinates, info))

// std::vector<coordinate> replacement that just
// keeps a running sum

struct accumulator
{
using value_type = coordinate;
using iterator = coordinate*;

std::size_t len = 0;

double x = 0;
double y = 0;
double z = 0;

void push_back( coordinate const& v )
{
x += v.x;
y += v.y;
z += v.z;

++len;
}

iterator
begin() { return nullptr; }

iterator
end() { return nullptr; }

void
clear()
{ }
};

struct coordinates2
{
accumulator coordinates;
std::string info;
};

BOOST_DESCRIBE_STRUCT(coordinates2, (), (coordinates, info))

using namespace std::chrono_literals;

int main()
{
// https://github.com/kostya/benchmarks/blob/master/json/generate_json.rb
std::ifstream is( "/tmp/1.json" );
std::string json( std::istreambuf_iterator<char>( is ), std::istreambuf_iterator<char>{} );

std::cout << "1.json: " << json.size() << " bytes\n";

// https://github.com/kostya/benchmarks/blob/master/json/test_boost_json.cpp
{
auto tp1 = std::chrono::steady_clock::now();

boost::json::value jv = boost::json::parse( json );

auto tp2 = std::chrono::steady_clock::now();
std::cout << "boost::json::parse: " << (tp2 - tp1) / 1ms << " ms\n";

auto x = 0.0, y = 0.0, z = 0.0;
auto len = 0;

auto &obj = jv.get_object();

for( auto& v: obj["coordinates"].get_array() )
{
++len;
auto& coord = v.get_object();
x += coord["x"].get_double();
y += coord["y"].get_double();
z += coord["z"].get_double();
}

x /= len;
y /= len;
z /= len;

auto tp3 = std::chrono::steady_clock::now();
std::cout << " x: " << x << ", y: " << y << ", z: " << z << ": " << (tp3 - tp2) / 1ms << " ms\n";
}

{
auto tp1 = std::chrono::steady_clock::now();

coordinates1 w;

boost::json::error_code ec;
boost::json::parse_into( w, json, ec );

if( ec.failed() )
{
std::cout << "Error: " << ec.what() << std::endl;
}

auto tp2 = std::chrono::steady_clock::now();
std::cout << "parse_into coordinates: " << (tp2 - tp1) / 1ms << " ms\n";

auto x = 0.0, y = 0.0, z = 0.0;
auto len = 0;

for( auto const& v: w.coordinates )
{
x += v.x;
y += v.y;
z += v.z;

++len;
}

x /= len;
y /= len;
z /= len;

auto tp3 = std::chrono::steady_clock::now();
std::cout << " x: " << x << ", y: " << y << ", z: " << z << ": " << (tp3 - tp2) / 1ms << " ms\n";
}

{
auto tp1 = std::chrono::steady_clock::now();

coordinates2 w;

boost::json::error_code ec;
boost::json::parse_into( w, json, ec );

if( ec.failed() )
{
std::cout << "Error: " << ec.what() << std::endl;
}

auto tp2 = std::chrono::steady_clock::now();
std::cout << "parse_into coordinates2: " << (tp2 - tp1) / 1ms << " ms\n";

double x = w.coordinates.x / w.coordinates.len;
double y = w.coordinates.y / w.coordinates.len;
double z = w.coordinates.z / w.coordinates.len;

auto tp3 = std::chrono::steady_clock::now();
std::cout << " x: " << x << ", y: " << y << ", z: " << z << ": " << (tp3 - tp2) / 1ms << " ms\n";
}
}

#endif
Loading