Dmitry's Q3 2023 Update
Oct 30, 2023In the third quarter my work was mostly focused on improvements to JSON’s
conversion features. In Boost.JSON conversion between user types and library
containers is done with value_to
and value_from
functions. Conversions of
composite types are attempted recursively. The library provides conversions for
several common groups of types, including sequences, associative containers,
tuples, numbers, and strings. Users also have the option to implement
conversion for other types. The function value_to
can fail at runtime, when
the structure of JSON value differs from the one expected by conversion
implementation. The function can report those errors in two separate ways:
exceptions and error codes. This is not only expressed in the
front-end—available overloads for value_to
—but is also supported on the
back-end. In other words, users can report errors from their conversions using
either exceptions or error codes. And the library turns error codes into
exceptions if on the front-end exceptions were requested, and even attempts the
opposite conversion where possible.
There was a problem though, when on the front-end exceptions were requested, and conversion of the deepest types are using exceptions too, but higher up errors are reported via error codes, there is a likelihood that the exception will be swallowed, and then a non-discriminate “some error has occurred” exception will be thrown instead. In order to fix this, I implemented a mechanism that communicates user’s choice of error reporting to the back-end.
Another change to the value_to/from
functions was the addition of
is_optional_like
and is_variant_like
traits. They determine if a type can
be classified as optional or variant correspondingly. Previously the library
explicitly handled std::optional
and std::variant
, and
support for boost::variant2::variant
was provided in its own library. With
these new traits all types that are sufficiently close to the standard optional
and variant are handled. In the case of optional it has an additional benefit:
conversion for described classes does not treat missing members as an error, if
their types are optionals.
But the biggest amount of time was spent on the new feature: direct parsing.
Even during the Boost review of Boost.JSON some people have complained that
they would prefer to avoid going through JSON containers entirely and parse
directly into their types. This is now possible with the function parse_into
.
Benchmarks also show that it can potentially double the performance. The design
and most of implementation was provided by Peter Dimov a while ago. So,
I mostly only had to refactor it to reduce code duplication, and change
behaviour of some functions, so that the result is to that of value_to/from
.
I also added an implementation for optionals, and provided a different
implementation for variants.
That last one deserves some explanation. Back when we were discussing different
ways to convert to and from variants, we chose a seamless approach, where the
variant itself doesn’t add anything to representation, but its current
alternative is represented directly. This is what most JSON files use in
practice, but it does complicate conversion back from JSON into variant.
value_to
attempts conversions for each alternative and picks the first one
which succeeds. This approach is not immediately possible for direct parsing,
though, as the choice of alternative has to be made before the full source of
the value is available. Peter’s solution was to only support variants for
which it was easy to discriminate between alternatives. This does eliminate
whole classes of JSON documents, though, in which variants of very similar
alternatives (usually objects) are used. So, I’ve dealt with variants
differently: the implementation instead records parser events and replays them
for the next alternative if the current one fails.
That approach has a consequence: for variants we need to dynamically allocate and keep a sequence of events. This can potentially eliminate all performance benefits, if the variant is the topmost container. To mitigate this I also implemented a way to limit the amount of parser events variant conversion can replay. For some variants a fairly small limit would be enough and completely eliminate the need for additional allocations. Ultimately, I decided not to merge that customisation, and wait for user feedback.
Finally, there was one notable change to Boost.JSON unrelated to conversion.
Different C++ implementations disagreed whether value jv{ value() }
copy-constructs jv
or uses construction from initializer_list
. This
resulted in code that behaves differently for different implementations.
Unfortunately we couldn’t fix it on our end, this is just a discrepancy between
how implementations treat this syntax. But eventually I came to realisation
that it can be handled explicitly to be a copy by the initializer_list
constructor. The constructor now treats initializer_list
s of size 1 as an
attempt to copy. If you do want an array of size 1, you can use value jv{
array{x} }
.
All Posts by This Author
- 04/29/2024 Dmitry's Q1 2024 Update
- 01/12/2024 Dmitry's Q4 2023 Update
- 10/30/2023 Dmitry's Q3 2023 Update
- 02/15/2021 Dmitry's January Update
- View All Posts...