2
0
mirror of https://github.com/boostorg/json.git synced 2026-01-19 04:12:14 +00:00

contextual conversions

This commit is contained in:
Dmitry Arkhipov
2022-09-26 11:23:59 +03:00
parent c3729e828a
commit b9e8b2edfe
21 changed files with 1926 additions and 393 deletions

View File

@@ -0,0 +1,123 @@
[/
Copyright (c) 2022 Dmitry Arkhipov (grisumbras@yandex.ru)
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/cppalliance/json
]
[/-----------------------------------------------------------------------------]
[section Contextual conversions]
Previously in this section we've been assuming that there is a particular
fitting JSON representation for a type. But this is not always the case. Often
one needs to represent particular value with JSON of certain format in one
situation and with another format in a different situation. This can be
achieved with Boost.JSON by providing an extra argument\U00002014context.
Let's implement conversion from `user_ns::ip_address` to a JSON string:
[doc_context_conversion_1]
These `tag_invoke` overloads take an extra `as_string` parameter, which
disambiguates this specific representation of `ip_address` from all other
potential representations. In order to take advantage of them one needs to pass
an `as_string` object to __value_from__ or __value_to__ as the last argument:
[doc_context_conversion_2]
Note, that if there is no dedicated `tag_invoke` overload for a given type and
a given context, the implementation falls back to overloads without context.
Thus it is easy to combine contextual conversions with conversions provided by
the library:
[doc_context_conversion_3]
[heading Conversions for third-party types]
Normally, you wouldn't be able to provide conversions for types from
third-party libraries and standard types, because it would require yout to put
`tag_invoke` overloads into namespaces you do not control. But with contexts
you can put the overloads into your namespaces. This is because the context
will add its associated namespaces into the list of namespaces where
`tag_invoke` overloads are searched.
As an example, let's implement conversion for
[@https://en.cppreference.com/w/cpp/chrono/system_clock
`std::chrono::system_clock::time_point`s]
using [@https://en.wikipedia.org/wiki/ISO_8601 ISO 8601] format.
[doc_context_conversion_4]
Reverse conversion is left out for brevity.
The new context is used in a similar fashion to `as_string` previously in this
section.
[doc_context_conversion_5]
One particular use case that is enabled by contexts is adaptor libraries that
define JSON conversion logic for types from a different library.
[heading Passing data to conversion functions]
Contexts we used so far were empty classes. But contexts may have data members
and member functions just as any class. Implementers of conversion functions
can take advantage of that to have conversions configurable at runtime or pass
special objects to conversions (e.g. allocators).
Let's rewrite conversion for `system_clock::time_point`s to allow any format
supported by `std::strftime`.
[doc_context_conversion_6]
This `tag_invoke` overload lets us change date conversion format at runtime.
Also note, that there is no ambiguity between `as_iso_8601` overload and
`date_format` overload. You can use both in your program:
[doc_context_conversion_7]
[heading Combining contexts]
Often it is needed to use several conversion contexts together. For example,
consider a log of remote users identified by IP addresses accessing a system.
We can represent it as `std::vector<
std::pair<std::chrono::system_clock::time_point, ip_address > >`. We want to
serialize both `ip_address`es and `time_point`s as strings, but for this we
need both `as_string` and `as_iso_8601` contexts. To combine several contexts
just use `std::tuple`. Conversion functions will select the first element of
the tuple for which a `tag_invoke` overload exists and will call that overload.
As usual, `tag_invoke` overloads that don't use contexts and library-provided
generic conversions are also supported. Thus, here's our example:
[doc_context_conversion_8]
In this snippet `time_point` is converted using `tag_invoke` overload that
takes `as_iso_8601`, `ip_address` is converted using `tag_invoke` overload
that takes `as_string`, and `std::vector` is converted using a generic
conversion provided by the library.
[heading Contexts and composite types]
As was shown previously, generic conversions provided by the library forward
contexts to conversions of nested objects. And in the case when you want to
provide your own conversion function for a composite type enabled by a
particular context, you usually also need to do that.
Consider this example. As was discussed in a previous section, __is_map_like__
requires that your key type satisfies __is_string_like__. Now, let's say your
keys are not string-like, but they do convert to __string__. You can make such
maps to also convert to objects using a context. But if you want to also use
another context for values, you need a way to pass the full combined context to
map elements. So, you want the following test to succeed.
[doc_context_conversion_9]
For this you will have to use a different overload of `tag_invoke`. This time
it has to be a function template, and it should have two parameters for
contexts. The first parameter is the specific context that disambiguates that
particular overload. The second parameter is the full context passed to
__value_to__ or __value_from__.
[doc_context_conversion_10]
[endsect]

View File

@@ -33,4 +33,9 @@ As discussed previously, we prefer to define a non-throwing overload of
__value_to__, as the latter can fallback to the former without performance
degradation.
Forward declarations of contextual conversions are done very similarly:
[doc_forward_conversion_3]
[doc_forward_conversion_4]
[endsect]

View File

@@ -21,6 +21,7 @@ from the standard library.
[include custom.qbk]
[include nothrow.qbk]
[include alloc.qbk]
[include context.qbk]
[include forward.qbk]
[endsect]

View File

@@ -20,5 +20,9 @@
[example_validate]
[endsect]
[section Allocator-aware conversion]
[example_use_allocator]
[endsect]
[endsect]

View File

@@ -115,6 +115,7 @@
[import ../../example/pretty.cpp]
[import ../../example/validate.cpp]
[import ../../example/use_allocator.cpp]
[import ../../include/boost/json/impl/serialize.ipp]
[import ../../test/doc_background.cpp]
[import ../../test/doc_parsing.cpp]

View File

@@ -49,3 +49,11 @@ add_executable(validate
)
set_property(TARGET validate PROPERTY FOLDER "example")
target_link_libraries(validate PRIVATE Boost::json)
#
add_executable(use_allocator
use_allocator.cpp
)
set_property(TARGET use_allocator PROPERTY FOLDER "example")
target_link_libraries(use_allocator PRIVATE Boost::json)

View File

@@ -16,3 +16,5 @@ exe pretty : pretty.cpp ;
exe proxy : proxy.cpp ;
exe validate : validate.cpp ;
exe use_allocator : use_allocator.cpp ;

89
example/use_allocator.cpp Normal file
View File

@@ -0,0 +1,89 @@
//
// Copyright (c) 2023 Dmitry Arkhipov (grisumbras@yandex.ru)
//
// 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
//
//[example_use_allocator
/*
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 >
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;
}
//]

View File

@@ -11,12 +11,20 @@
#define BOOST_JSON_CONVERSION_HPP
#include <boost/json/detail/config.hpp>
#include <boost/json/fwd.hpp>
#include <type_traits>
namespace boost {
namespace json {
namespace detail {
template< class Ctx, class T, class Dir >
struct supported_context;
} // namespace detail
/** Customization point tag.
This tag type is used by the function

View File

@@ -25,9 +25,10 @@ namespace json {
namespace detail {
template <class T>
template< class Ctx, class T >
struct append_tuple_element {
array& arr;
Ctx const& ctx;
T&& t;
template<std::size_t I>
@@ -36,35 +37,52 @@ struct append_tuple_element {
{
using std::get;
arr.emplace_back(value_from(
get<I>(std::forward<T>(t)), arr.storage()));
get<I>(std::forward<T>(t)), ctx, arr.storage() ));
}
};
//----------------------------------------------------------
// User-provided conversion
template<class T>
template< class T, class Ctx >
void
value_from_impl( user_conversion_tag, value& jv, T&& from )
value_from_impl( user_conversion_tag, value& jv, T&& from, Ctx const& )
{
tag_invoke(value_from_tag(), jv, std::forward<T>(from));
tag_invoke( value_from_tag(), jv, static_cast<T&&>(from) );
}
template< class T, class Ctx >
void
value_from_impl( context_conversion_tag, value& jv, T&& from, Ctx const& ctx)
{
using Sup = supported_context<Ctx, T, value_from_conversion>;
tag_invoke( value_from_tag(), jv, static_cast<T&&>(from), Sup::get(ctx) );
}
template< class T, class Ctx >
void
value_from_impl(
full_context_conversion_tag, value& jv, T&& from, Ctx const& ctx)
{
using Sup = supported_context<Ctx, T, value_from_conversion>;
tag_invoke(
value_from_tag(), jv, static_cast<T&&>(from), Sup::get(ctx), ctx );
}
//----------------------------------------------------------
// Native conversion
template<class T>
template< class T, class Ctx >
void
value_from_impl( native_conversion_tag, value& jv, T&& from )
value_from_impl( native_conversion_tag, value& jv, T&& from, Ctx const& )
{
jv = std::forward<T>(from);
}
// null-like types
template<class T>
template< class T, class Ctx >
void
value_from_impl( null_like_conversion_tag, value& jv, T&& )
value_from_impl( null_like_conversion_tag, value& jv, T&&, Ctx const& )
{
// do nothing
BOOST_ASSERT(jv.is_null());
@@ -72,58 +90,62 @@ value_from_impl( null_like_conversion_tag, value& jv, T&& )
}
// string-like types
template<class T>
template< class T, class Ctx >
void
value_from_impl( string_like_conversion_tag, value& jv, T&& from )
value_from_impl( string_like_conversion_tag, value& jv, T&& from, Ctx const& )
{
auto sv = static_cast<string_view>(from);
jv.emplace_string().assign(sv);
}
// map-like types
template<class T>
template< class T, class Ctx >
void
value_from_impl( map_like_conversion_tag, value& jv, T&& from )
value_from_impl( map_like_conversion_tag, value& jv, T&& from, Ctx const& ctx )
{
using std::get;
object& obj = jv.emplace_object();
obj.reserve(detail::try_size(from, size_implementation<T>()));
for (auto&& elem : from)
obj.emplace(get<0>(elem), value_from(
get<1>(elem), obj.storage()));
obj.emplace(
get<0>(elem),
value_from( get<1>(elem), ctx, obj.storage() ));
}
// ranges
template<class T>
template< class T, class Ctx >
void
value_from_impl( sequence_conversion_tag, value& jv, T&& from )
value_from_impl( sequence_conversion_tag, value& jv, T&& from, Ctx const& ctx )
{
array& result = jv.emplace_array();
result.reserve(detail::try_size(from, size_implementation<T>()));
using ForwardedValue = forwarded_value<T&&>;
for (auto&& elem : from)
result.emplace_back(
value_from(
static_cast< forwarded_value<T&&> >(elem),
// not a static_cast in order to appease clang < 4.0
ForwardedValue(elem),
ctx,
result.storage() ));
}
// tuple-like types
template<class T>
template< class T, class Ctx >
void
value_from_impl( tuple_conversion_tag, value& jv, T&& from )
value_from_impl( tuple_conversion_tag, value& jv, T&& from, Ctx const& ctx )
{
constexpr std::size_t n =
std::tuple_size<remove_cvref<T>>::value;
array& arr = jv.emplace_array();
arr.reserve(n);
mp11::mp_for_each<mp11::mp_iota_c<n>>(
append_tuple_element<T>{arr, std::forward<T>(from)});
append_tuple_element< Ctx, T >{ arr, ctx, std::forward<T>(from) });
}
// no suitable conversion implementation
template<class T>
template< class T, class Ctx >
void
value_from_impl( no_conversion_tag, value&, T&& )
value_from_impl( no_conversion_tag, value&, T&&, Ctx const& )
{
static_assert(
!std::is_same<T, T>::value,
@@ -131,26 +153,29 @@ value_from_impl( no_conversion_tag, value&, T&& )
}
#ifndef BOOST_NO_CXX17_HDR_VARIANT
template< class Ctx >
struct value_from_visitor
{
value& jv;
Ctx const& ctx;
template<class T>
void
operator()(T&& t)
{
value_from(static_cast<T&&>(t), jv);
value_from( static_cast<T&&>(t), ctx, jv );
}
};
#endif // BOOST_NO_CXX17_HDR_VARIANT
template< class T >
template< class Ctx, class T >
struct from_described_member
{
using Ds = describe::describe_members<
remove_cvref<T>, describe::mod_public | describe::mod_inherited>;
object& obj;
Ctx const& ctx;
T&& from;
template< class I >
@@ -162,17 +187,20 @@ struct from_described_member
D::name,
value_from(
static_cast<T&&>(from).* D::pointer,
ctx,
obj.storage()));
}
};
// described classes
template<class T>
template< class T, class Ctx >
void
value_from_impl( described_class_conversion_tag, value& jv, T&& from )
value_from_impl(
described_class_conversion_tag, value& jv, T&& from, Ctx const& ctx )
{
object& obj = jv.emplace_object();
from_described_member<T> member_converter{obj, static_cast<T&&>(from)};
from_described_member<Ctx, T> member_converter{
obj, ctx, static_cast<T&&>(from)};
using Ds = typename decltype(member_converter)::Ds;
constexpr std::size_t N = mp11::mp_size<Ds>::value;
@@ -181,9 +209,10 @@ value_from_impl( described_class_conversion_tag, value& jv, T&& from )
}
// described enums
template<class T>
template< class T, class Ctx >
void
value_from_impl( described_enum_conversion_tag, value& jv, T from )
value_from_impl(
described_enum_conversion_tag, value& jv, T from, Ctx const& )
{
(void)jv;
(void)from;
@@ -202,34 +231,34 @@ value_from_impl( described_enum_conversion_tag, value& jv, T from )
#endif
}
template<class T>
using value_from_category = conversion_category<T, value_from_conversion>;
//----------------------------------------------------------
// Contextual conversions
template< class Ctx, class T >
using value_from_category = conversion_category<
Ctx, T, value_from_conversion >;
} // detail
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
template<class T>
template< class T, class Ctx >
void
tag_invoke(
value_from_tag,
value& jv,
std::optional<T> const& from)
value_from_tag, value& jv, std::optional<T> const& from, Ctx const& ctx )
{
if( from )
value_from(*from, jv);
value_from( *from, ctx, jv );
else
jv = nullptr;
}
template<class T>
template< class T, class Ctx >
void
tag_invoke(
value_from_tag,
value& jv,
std::optional<T>&& from)
value_from_tag, value& jv, std::optional<T>&& from, Ctx const& ctx )
{
if( from )
value_from(std::move(*from), jv);
value_from( std::move(*from), ctx, jv );
else
jv = nullptr;
}
@@ -249,24 +278,23 @@ tag_invoke(
#ifndef BOOST_NO_CXX17_HDR_VARIANT
// std::variant
template<class... Ts>
template< class Ctx, class... Ts >
void
tag_invoke(
value_from_tag,
value& jv,
std::variant<Ts...>&& from)
value_from_tag, value& jv, std::variant<Ts...>&& from, Ctx const& ctx )
{
std::visit(detail::value_from_visitor{jv}, std::move(from));
std::visit( detail::value_from_visitor<Ctx>{ jv, ctx }, std::move(from) );
}
template<class... Ts>
template< class Ctx, class... Ts >
void
tag_invoke(
value_from_tag,
value& jv,
std::variant<Ts...> const& from)
std::variant<Ts...> const& from,
Ctx const& ctx )
{
std::visit(detail::value_from_visitor{jv}, from);
std::visit( detail::value_from_visitor<Ctx>{ jv, ctx }, from );
}
#endif // BOOST_NO_CXX17_HDR_VARIANT

View File

@@ -23,16 +23,6 @@
namespace boost {
namespace json {
template<class T, class U,
typename std::enable_if<
! std::is_reference<T>::value &&
std::is_same<U, value>::value>::type>
T value_to(U const&);
template<class T>
typename result_for<T, value>::type
try_value_to(const value& jv);
namespace detail {
template<class T>
@@ -122,25 +112,33 @@ inserter(
}
// identity conversion
inline
template< class Ctx >
result<value>
value_to_impl( value_conversion_tag, try_value_to_tag<value>, value const& jv )
value_to_impl(
value_conversion_tag,
try_value_to_tag<value>,
value const& jv,
Ctx const& )
{
return jv;
}
inline
template< class Ctx >
value
value_to_impl( value_conversion_tag, value_to_tag<value>, value const& jv )
value_to_impl(
value_conversion_tag, value_to_tag<value>, value const& jv, Ctx const& )
{
return jv;
}
// object
inline
template< class Ctx >
result<object>
value_to_impl(
object_conversion_tag, try_value_to_tag<object>, value const& jv )
object_conversion_tag,
try_value_to_tag<object>,
value const& jv,
Ctx const& )
{
object const* obj = jv.if_object();
if( obj )
@@ -151,10 +149,13 @@ value_to_impl(
}
// array
inline
template< class Ctx >
result<array>
value_to_impl(
array_conversion_tag, try_value_to_tag<array>, value const& jv )
array_conversion_tag,
try_value_to_tag<array>,
value const& jv,
Ctx const& )
{
array const* arr = jv.if_array();
if( arr )
@@ -165,10 +166,13 @@ value_to_impl(
}
// string
inline
template< class Ctx >
result<string>
value_to_impl(
string_conversion_tag, try_value_to_tag<string>, value const& jv )
string_conversion_tag,
try_value_to_tag<string>,
value const& jv,
Ctx const& )
{
string const* str = jv.if_string();
if( str )
@@ -179,9 +183,10 @@ value_to_impl(
}
// bool
inline
template< class Ctx >
result<bool>
value_to_impl( bool_conversion_tag, try_value_to_tag<bool>, value const& jv )
value_to_impl(
bool_conversion_tag, try_value_to_tag<bool>, value const& jv, Ctx const& )
{
auto b = jv.if_bool();
if( b )
@@ -192,9 +197,10 @@ value_to_impl( bool_conversion_tag, try_value_to_tag<bool>, value const& jv )
}
// integral and floating point
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl( number_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
number_conversion_tag, try_value_to_tag<T>, value const& jv, Ctx const& )
{
error_code ec;
auto const n = jv.to_number<T>(ec);
@@ -204,9 +210,13 @@ value_to_impl( number_conversion_tag, try_value_to_tag<T>, value const& jv )
}
// null-like conversion
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl( null_like_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
null_like_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& )
{
if( jv.is_null() )
return {boost::system::in_place_value, T{}};
@@ -216,10 +226,13 @@ value_to_impl( null_like_conversion_tag, try_value_to_tag<T>, value const& jv )
}
// string-like types
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl(
string_like_conversion_tag, try_value_to_tag<T>, value const& jv )
string_like_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& )
{
auto str = jv.if_string();
if( str )
@@ -230,9 +243,13 @@ value_to_impl(
}
// map-like containers
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl( map_like_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
map_like_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
error_code ec;
@@ -251,7 +268,7 @@ value_to_impl( map_like_conversion_tag, try_value_to_tag<T>, value const& jv )
auto ins = detail::inserter(res, inserter_implementation<T>());
for( key_value_pair const& kv: *obj )
{
auto elem_res = try_value_to<mapped_type<T>>(kv.value());
auto elem_res = try_value_to<mapped_type<T>>( kv.value(), ctx );
if( elem_res.has_error() )
return {boost::system::in_place_error, elem_res.error()};
*ins++ = value_type<T>{
@@ -261,9 +278,10 @@ value_to_impl( map_like_conversion_tag, try_value_to_tag<T>, value const& jv )
return res;
}
template<class T>
template< class T, class Ctx >
T
value_to_impl( map_like_conversion_tag, value_to_tag<T>, value const& jv )
value_to_impl(
map_like_conversion_tag, value_to_tag<T>, value const& jv, Ctx const& ctx )
{
error_code ec;
@@ -283,14 +301,18 @@ value_to_impl( map_like_conversion_tag, value_to_tag<T>, value const& jv )
for( key_value_pair const& kv: *obj )
*ins++ = value_type<T>{
key_type<T>(kv.key()),
value_to<mapped_type<T>>(kv.value())};
value_to<mapped_type<T>>( kv.value(), ctx )};
return result;
}
// all other containers
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl( sequence_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
sequence_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
error_code ec;
@@ -309,7 +331,7 @@ value_to_impl( sequence_conversion_tag, try_value_to_tag<T>, value const& jv )
auto ins = detail::inserter(result, inserter_implementation<T>());
for( value const& val: *arr )
{
auto elem_res = try_value_to<value_type<T>>(val);
auto elem_res = try_value_to<value_type<T>>( val, ctx );
if( elem_res.has_error() )
return {boost::system::in_place_error, elem_res.error()};
*ins++ = std::move(*elem_res);
@@ -317,9 +339,10 @@ value_to_impl( sequence_conversion_tag, try_value_to_tag<T>, value const& jv )
return result;
}
template<class T>
template< class T, class Ctx >
T
value_to_impl( sequence_conversion_tag, value_to_tag<T>, value const& jv )
value_to_impl(
sequence_conversion_tag, value_to_tag<T>, value const& jv, Ctx const& ctx )
{
error_code ec;
@@ -337,31 +360,32 @@ value_to_impl( sequence_conversion_tag, value_to_tag<T>, value const& jv )
auto ins = detail::inserter(result, inserter_implementation<T>());
for( value const& val: *arr )
*ins++ = value_to<value_type<T>>(val);
*ins++ = value_to<value_type<T>>( val, ctx );
return result;
}
// tuple-like types
template <class T>
template< class T, class Ctx >
result<T>
try_make_tuple_elem(value const& jv, error_code& ec)
try_make_tuple_elem(value const& jv, Ctx const& ctx, error_code& ec)
{
if( ec.failed() )
return {boost::system::in_place_error, ec};
auto result = try_value_to<T>(jv);
auto result = try_value_to<T>( jv, ctx );
ec = result.error();
return result;
}
template <class T, std::size_t... Is>
template <class T, class Ctx, std::size_t... Is>
result<T>
try_make_tuple_like(array const& arr, boost::mp11::index_sequence<Is...>)
try_make_tuple_like(
array const& arr, Ctx const& ctx, boost::mp11::index_sequence<Is...>)
{
error_code ec;
auto items = std::make_tuple(
try_make_tuple_elem<tuple_element_t<Is, T>>(
arr[Is], ec)
arr[Is], ctx, ec)
...);
if( ec.failed() )
return {boost::system::in_place_error, ec};
@@ -370,16 +394,21 @@ try_make_tuple_like(array const& arr, boost::mp11::index_sequence<Is...>)
boost::system::in_place_value, T(std::move(*std::get<Is>(items))...)};
}
template <class T, std::size_t... Is>
template <class T, class Ctx, std::size_t... Is>
T
make_tuple_like(array const& arr, boost::mp11::index_sequence<Is...>)
make_tuple_like(
array const& arr, Ctx const& ctx, boost::mp11::index_sequence<Is...>)
{
return T(value_to<tuple_element_t<Is, T>>(arr[Is])...);
return T( value_to<tuple_element_t<Is, T>>( arr[Is], ctx )... );
}
template <class T>
template< class T, class Ctx >
result<T>
value_to_impl( tuple_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
tuple_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
error_code ec;
@@ -398,12 +427,13 @@ value_to_impl( tuple_conversion_tag, try_value_to_tag<T>, value const& jv )
}
return try_make_tuple_like<T>(
*arr, boost::mp11::make_index_sequence<N>());
*arr, ctx, boost::mp11::make_index_sequence<N>());
}
template <class T>
template< class T, class Ctx >
T
value_to_impl( tuple_conversion_tag, value_to_tag<T>, value const& jv )
value_to_impl(
tuple_conversion_tag, value_to_tag<T>, value const& jv, Ctx const& ctx )
{
error_code ec;
@@ -422,7 +452,7 @@ value_to_impl( tuple_conversion_tag, value_to_tag<T>, value const& jv )
}
return make_tuple_like<T>(
*arr, boost::mp11::make_index_sequence<N>());
*arr, ctx, boost::mp11::make_index_sequence<N>());
}
template< class T>
@@ -437,7 +467,7 @@ struct is_optional< std::optional<T> >
{ };
#endif // BOOST_NO_CXX17_HDR_OPTIONAL
template< class T >
template< class Ctx, class T >
struct to_described_member
{
using Ds = describe::describe_members<
@@ -450,6 +480,7 @@ struct to_described_member
result<T>& res;
object const& obj;
std::size_t count;
Ctx const& ctx;
template< class I >
void
@@ -478,7 +509,7 @@ struct to_described_member
# pragma GCC diagnostic ignored "-Wunused"
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
auto member_res = try_value_to<M>(found->value());
auto member_res = try_value_to<M>( found->value(), ctx );
#if defined(__GNUC__) && BOOST_GCC_VERSION >= 80000 && BOOST_GCC_VERSION < 11000
# pragma GCC diagnostic pop
#endif
@@ -493,10 +524,13 @@ struct to_described_member
};
// described classes
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl(
described_class_conversion_tag, try_value_to_tag<T>, value const& jv )
described_class_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
result<T> res;
@@ -509,7 +543,7 @@ value_to_impl(
return res;
}
to_described_member<T> member_converter{res, *obj, 0u};
to_described_member< Ctx, T > member_converter{ res, *obj, 0u, ctx };
using Ds = typename decltype(member_converter)::Ds;
constexpr std::size_t N = mp11::mp_size<Ds>::value;
@@ -530,10 +564,13 @@ value_to_impl(
}
// described enums
template<class T>
template< class T, class Ctx >
result<T>
value_to_impl(
described_enum_conversion_tag, try_value_to_tag<T>, value const& jv )
described_enum_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& )
{
T val = {};
(void)jv;
@@ -559,20 +596,22 @@ value_to_impl(
//----------------------------------------------------------
// User-provided conversion
template<class T>
template< class T, class Ctx >
typename std::enable_if<
mp11::mp_valid<has_user_conversion_to_impl, T>::value,
T>::type
value_to_impl( user_conversion_tag, value_to_tag<T> tag, value const& jv )
value_to_impl(
user_conversion_tag, value_to_tag<T> tag, value const& jv, Ctx const&)
{
return tag_invoke(tag, jv);
}
template<class T>
template< class T, class Ctx >
typename std::enable_if<
!mp11::mp_valid<has_user_conversion_to_impl, T>::value,
T>::type
value_to_impl( user_conversion_tag, value_to_tag<T>, value const& jv )
value_to_impl(
user_conversion_tag, value_to_tag<T>, value const& jv, Ctx const& )
{
auto res = tag_invoke(try_value_to_tag<T>(), jv);
if( res.has_error() )
@@ -580,20 +619,22 @@ value_to_impl( user_conversion_tag, value_to_tag<T>, value const& jv )
return std::move(*res);
}
template<class T>
template< class T, class Ctx >
typename std::enable_if<
mp11::mp_valid<has_nonthrowing_user_conversion_to_impl, T>::value,
result<T>>::type
value_to_impl( user_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
user_conversion_tag, try_value_to_tag<T>, value const& jv, Ctx const& )
{
return tag_invoke(try_value_to_tag<T>(), jv);
}
template<class T>
template< class T, class Ctx >
typename std::enable_if<
!mp11::mp_valid<has_nonthrowing_user_conversion_to_impl, T>::value,
result<T>>::type
value_to_impl( user_conversion_tag, try_value_to_tag<T>, value const& jv )
value_to_impl(
user_conversion_tag, try_value_to_tag<T>, value const& jv, Ctx const& )
{
try
{
@@ -616,10 +657,217 @@ value_to_impl( user_conversion_tag, try_value_to_tag<T>, value const& jv )
}
}
//----------------------------------------------------------
// User-provided context conversion
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
mp11::mp_valid<
has_context_conversion_to_impl,
typename Sup::type,
T>::value,
T>::type
value_to_impl(
context_conversion_tag,
value_to_tag<T> tag,
value const& jv,
Ctx const& ctx )
{
return tag_invoke( tag, jv, Sup::get(ctx) );
}
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
!mp11::mp_valid<
has_context_conversion_to_impl,
typename Sup::type,
T>::value,
T>::type
value_to_impl(
context_conversion_tag, value_to_tag<T>, value const& jv, Ctx const& ctx )
{
auto res = tag_invoke( try_value_to_tag<T>(), jv, Sup::get(ctx) );
if( res.has_error() )
throw_system_error( res.error() );
return std::move(*res);
}
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
mp11::mp_valid<
has_nonthrowing_context_conversion_to_impl,
typename Sup::type,
T>::value,
result<T>>::type
value_to_impl(
context_conversion_tag,
try_value_to_tag<T> tag,
value const& jv,
Ctx const& ctx )
{
return tag_invoke( tag, jv, Sup::get(ctx) );
}
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
!mp11::mp_valid<
has_nonthrowing_context_conversion_to_impl,
typename Sup::type,
T>::value,
result<T>>::type
value_to_impl(
context_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
try
{
return {
boost::system::in_place_value,
tag_invoke( value_to_tag<T>(), jv, Sup::get(ctx) )};
}
catch( std::bad_alloc const&)
{
throw;
}
catch( system_error const& e)
{
return {boost::system::in_place_error, e.code()};
}
catch( ... )
{
error_code ec;
BOOST_JSON_FAIL(ec, error::exception);
return {boost::system::in_place_error, ec};
}
}
//----------------------------------------------------------
// User-provided full context conversion
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
mp11::mp_valid<
has_full_context_conversion_to_impl,
typename Sup::type,
T>::value,
T>::type
value_to_impl(
full_context_conversion_tag,
value_to_tag<T> tag,
value const& jv,
Ctx const& ctx )
{
return tag_invoke( tag, jv, Sup::get(ctx), ctx );
}
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
!mp11::mp_valid<
has_full_context_conversion_to_impl,
typename Sup::type,
T>::value,
T>::type
value_to_impl(
full_context_conversion_tag,
value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
auto res = tag_invoke( try_value_to_tag<T>(), jv, Sup::get(ctx), ctx );
if( res.has_error() )
throw_system_error( res.error() );
return std::move(*res);
}
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
mp11::mp_valid<
has_nonthrowing_full_context_conversion_to_impl,
typename Sup::type,
T>::value,
result<T>>::type
value_to_impl(
full_context_conversion_tag,
try_value_to_tag<T> tag,
value const& jv,
Ctx const& ctx )
{
return tag_invoke( tag, jv, Sup::get(ctx), ctx );
}
template<
class T,
class Ctx,
class Sup = supported_context<Ctx, T, value_to_conversion>
>
typename std::enable_if<
!mp11::mp_valid<
has_nonthrowing_full_context_conversion_to_impl,
typename Sup::type,
T>::value,
result<T>>::type
value_to_impl(
full_context_conversion_tag,
try_value_to_tag<T>,
value const& jv,
Ctx const& ctx )
{
try
{
return {
boost::system::in_place_value,
tag_invoke( value_to_tag<T>(), jv, Sup::get(ctx), ctx )};
}
catch( std::bad_alloc const&)
{
throw;
}
catch( system_error const& e)
{
return {boost::system::in_place_error, e.code()};
}
catch( ... )
{
error_code ec;
BOOST_JSON_FAIL(ec, error::exception);
return {boost::system::in_place_error, ec};
}
}
// no suitable conversion implementation
template<class T>
template< class T, class Ctx >
T
value_to_impl( no_conversion_tag, value_to_tag<T>, value const& )
value_to_impl( no_conversion_tag, value_to_tag<T>, value const&, Ctx const& )
{
static_assert(
!std::is_same<T, T>::value,
@@ -627,15 +875,16 @@ value_to_impl( no_conversion_tag, value_to_tag<T>, value const& )
}
// generic wrapper over non-throwing implementations
template<class T, class Impl>
template< class Impl, class T, class Ctx >
T
value_to_impl( Impl impl, value_to_tag<T>, value const& jv )
value_to_impl( Impl impl, value_to_tag<T>, value const& jv, Ctx const& ctx )
{
return value_to_impl( impl, try_value_to_tag<T>(), jv ).value();
return value_to_impl( impl, try_value_to_tag<T>(), jv, ctx ).value();
}
template<class T>
using value_to_category = conversion_category<T, value_to_conversion>;
template< class Ctx, class T >
using value_to_category = conversion_category<
Ctx, T, value_to_conversion >;
} // detail

View File

@@ -63,13 +63,23 @@ struct is_described_enum;
template<class T>
void value_from( T&& t, value& jv );
template<class T, class Context>
void value_from( T&& t, value& jv, Context const& ctx );
template<class T>
T value_to( value const & v );
template<class T, class Context>
T value_to( value const & v, Context const& ctx );
template<class T>
typename result_for<T, value>::type
try_value_to( value const & jv );
template<class T, class Context>
typename result_for<T, value>::type
try_value_to( value const & jv, Context const& ctx );
template<class T>
typename result_for<T, value>::type
result_from_errno( int e, boost::source_location const* loc ) noexcept;

View File

@@ -22,6 +22,7 @@
#include <iterator>
#include <utility>
#include <tuple>
#ifndef BOOST_NO_CXX17_HDR_VARIANT
# include <variant>
#endif // BOOST_NO_CXX17_HDR_VARIANT
@@ -135,6 +136,8 @@ using value_from_conversion = mp11::mp_true;
using value_to_conversion = mp11::mp_false;
struct user_conversion_tag { };
struct context_conversion_tag : user_conversion_tag { };
struct full_context_conversion_tag : context_conversion_tag { };
struct native_conversion_tag { };
struct value_conversion_tag : native_conversion_tag { };
struct object_conversion_tag : native_conversion_tag { };
@@ -151,25 +154,61 @@ struct described_class_conversion_tag { };
struct described_enum_conversion_tag { };
struct no_conversion_tag { };
template<class... Args>
using supports_tag_invoke = decltype(tag_invoke( std::declval<Args>()... ));
template<class T>
using has_user_conversion_from_impl
= decltype(tag_invoke(
value_from_tag(), std::declval<value&>(), std::declval<T&&>()));
using has_user_conversion_from_impl = supports_tag_invoke<
value_from_tag, value&, T&& >;
template<class T>
using has_user_conversion_to_impl
= decltype(tag_invoke(value_to_tag<T>(), std::declval<value const &>()));
using has_user_conversion_to_impl = supports_tag_invoke<
value_to_tag<T>, value const& >;
template<class T>
using has_nonthrowing_user_conversion_to_impl
= decltype(tag_invoke(
try_value_to_tag<T>(), std::declval<value const&>() ));
template<class T, class Dir>
using has_user_conversion = mp11::mp_if<
using has_nonthrowing_user_conversion_to_impl = supports_tag_invoke<
try_value_to_tag<T>, value const& >;
template< class T, class Dir >
using has_user_conversion1 = mp11::mp_if<
std::is_same<Dir, value_from_conversion>,
mp11::mp_valid<has_user_conversion_from_impl, T>,
mp11::mp_or<
mp11::mp_valid<has_user_conversion_to_impl, T>,
mp11::mp_valid<has_nonthrowing_user_conversion_to_impl, T>>>;
template< class Ctx, class T >
using has_context_conversion_from_impl = supports_tag_invoke<
value_from_tag, value&, T&&, Ctx const& >;
template< class Ctx, class T >
using has_context_conversion_to_impl = supports_tag_invoke<
value_to_tag<T>, value const&, Ctx const& >;
template< class Ctx, class T >
using has_nonthrowing_context_conversion_to_impl = supports_tag_invoke<
try_value_to_tag<T>, value const&, Ctx const& >;
template< class Ctx, class T, class Dir >
using has_user_conversion2 = mp11::mp_if<
std::is_same<Dir, value_from_conversion>,
mp11::mp_valid<has_context_conversion_from_impl, Ctx, T>,
mp11::mp_or<
mp11::mp_valid<has_context_conversion_to_impl, Ctx, T>,
mp11::mp_valid<has_nonthrowing_context_conversion_to_impl, Ctx, T>>>;
template< class Ctx, class T >
using has_full_context_conversion_from_impl = supports_tag_invoke<
value_from_tag, value&, T&&, Ctx const&, Ctx const& >;
template< class Ctx, class T >
using has_full_context_conversion_to_impl = supports_tag_invoke<
value_to_tag<T>, value const&, Ctx const&, Ctx const& >;
template< class Ctx, class T >
using has_nonthrowing_full_context_conversion_to_impl = supports_tag_invoke<
try_value_to_tag<T>, value const&, Ctx const&, Ctx const& >;
template< class Ctx, class T, class Dir >
using has_user_conversion3 = mp11::mp_if<
std::is_same<Dir, value_from_conversion>,
mp11::mp_valid<has_full_context_conversion_from_impl, Ctx, T>,
mp11::mp_or<
mp11::mp_valid<has_full_context_conversion_to_impl, Ctx, T>,
mp11::mp_valid<
has_nonthrowing_full_context_conversion_to_impl, Ctx, T>>>;
template< class T >
using described_non_public_members = describe::describe_members<
T, describe::mod_private | describe::mod_protected>;
@@ -177,43 +216,81 @@ template< class T >
using described_bases = describe::describe_bases<
T, describe::mod_any_access>;
template<class T, class Dir>
using conversion_category = mp11::mp_cond<
// user conversion (via tag_invoke)
has_user_conversion<T, Dir>, user_conversion_tag,
// native conversions (constructors and member functions of value)
std::is_same<T, value>, value_conversion_tag,
std::is_same<T, array>, array_conversion_tag,
std::is_same<T, object>, object_conversion_tag,
std::is_same<T, string>, string_conversion_tag,
std::is_same<T, bool>, bool_conversion_tag,
std::is_arithmetic<T>, number_conversion_tag,
// generic conversions
is_null_like<T>, null_like_conversion_tag,
is_string_like<T>, string_like_conversion_tag,
is_map_like<T>, map_like_conversion_tag,
is_sequence_like<T>, sequence_conversion_tag,
is_tuple_like<T>, tuple_conversion_tag,
is_described_class<T>, described_class_conversion_tag,
is_described_enum<T>, described_enum_conversion_tag,
// failed to find a suitable implementation
mp11::mp_true, no_conversion_tag>;
template< class Ctx, class T, class Dir >
struct conversion_category_impl
{
using type = mp11::mp_cond<
// user conversion (via tag_invoke)
has_user_conversion3<Ctx, T, Dir>, full_context_conversion_tag,
has_user_conversion2<Ctx, T, Dir>, context_conversion_tag,
has_user_conversion1<T, Dir>, user_conversion_tag,
// native conversions (constructors and member functions of value)
std::is_same<T, value>, value_conversion_tag,
std::is_same<T, array>, array_conversion_tag,
std::is_same<T, object>, object_conversion_tag,
std::is_same<T, string>, string_conversion_tag,
std::is_same<T, bool>, bool_conversion_tag,
std::is_arithmetic<T>, number_conversion_tag,
// generic conversions
is_null_like<T>, null_like_conversion_tag,
is_string_like<T>, string_like_conversion_tag,
is_map_like<T>, map_like_conversion_tag,
is_sequence_like<T>, sequence_conversion_tag,
is_tuple_like<T>, tuple_conversion_tag,
is_described_class<T>, described_class_conversion_tag,
is_described_enum<T>, described_enum_conversion_tag,
// failed to find a suitable implementation
mp11::mp_true, no_conversion_tag>;
};
template< class Ctx, class T, class Dir >
using conversion_category =
typename conversion_category_impl< Ctx, T, Dir >::type;
template< class T >
using any_conversion_tag = mp11::mp_not<
std::is_same< T, no_conversion_tag > >;
template< class T, class Dir, class... Ctxs >
struct conversion_category_impl< std::tuple<Ctxs...>, T, Dir >
{
using ctxs = mp11::mp_list< remove_cvref<Ctxs>... >;
using cats = mp11::mp_list<
conversion_category<remove_cvref<Ctxs>, T, Dir>... >;
template< class I >
using exists = mp11::mp_less< I, mp11::mp_size<cats> >;
using context2 = mp11::mp_find< cats, full_context_conversion_tag >;
using context1 = mp11::mp_find< cats, context_conversion_tag >;
using context0 = mp11::mp_find< cats, user_conversion_tag >;
using index = mp11::mp_cond<
exists<context2>, context2,
exists<context1>, context1,
exists<context0>, context0,
mp11::mp_true, mp11::mp_find_if< cats, any_conversion_tag > >;
using type = mp11::mp_eval_or<
no_conversion_tag,
mp11::mp_at, cats, index >;
};
struct no_context
{};
template <class T, class Dir>
using can_convert = mp11::mp_not<
std::is_same<
detail::conversion_category<T, Dir>,
detail::conversion_category<no_context, T, Dir>,
detail::no_conversion_tag>>;
template<class Impl1, class Impl2>
using conversion_round_trips_helper = mp11::mp_or<
std::is_same<Impl1, Impl2>,
std::is_same<user_conversion_tag, Impl1>,
std::is_same<user_conversion_tag, Impl2>>;
template<class T, class Dir>
std::is_base_of<user_conversion_tag, Impl1>,
std::is_base_of<user_conversion_tag, Impl2>>;
template< class Ctx, class T, class Dir >
using conversion_round_trips = conversion_round_trips_helper<
conversion_category<T, Dir>,
conversion_category<T, mp11::mp_not<Dir>>>;
conversion_category<Ctx, T, Dir>,
conversion_category<Ctx, T, mp11::mp_not<Dir>>>;
template< class T1, class T2 >
struct copy_cref_helper
@@ -251,6 +328,37 @@ template< class Rng >
using forwarded_value = forwarded_value_helper<
Rng, iterator_traits< Rng > >;
template< class Ctx, class T, class Dir >
struct supported_context
{
using type = Ctx;
static
type const&
get( Ctx const& ctx ) noexcept
{
return ctx;
}
};
template< class T, class Dir, class... Ctxs >
struct supported_context< std::tuple<Ctxs...>, T, Dir >
{
using Ctx = std::tuple<Ctxs...>;
using impl = conversion_category_impl<Ctx, T, Dir>;
using index = typename impl::index;
using next_supported = supported_context<
mp11::mp_at< typename impl::ctxs, index >, T, Dir >;
using type = typename next_supported::type;
static
type const&
get( Ctx const& ctx ) noexcept
{
return next_supported::get( std::get<index::value>( ctx ) );
}
};
} // namespace detail
template <class T>

View File

@@ -17,6 +17,162 @@
namespace boost {
namespace json {
/** Convert an object of type `T` to @ref value.
This function attempts to convert an object
of type `T` to @ref value using
@li one of @ref value's constructors,
@li a library-provided generic conversion, or
@li a user-provided overload of `tag_invoke`.
Out of the box the function supports types satisfying
<a href="https://en.cppreference.com/w/cpp/named_req/SequenceContainer"><em>SequenceContainer</em></a>,
arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`,
`std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`.
Conversion of other types is done by calling an overload of `tag_invoke`
found by argument-dependent lookup. Its signature should be similar to:
@code
template< class FullContext >
void tag_invoke( value_from_tag, value&, T, const Context& , const FullContext& );
@endcode
or
@code
void tag_invoke( value_from_tag, value&, T, const Context& );
@endcode
or
@code
void tag_invoke( value_from_tag, value&, T );
@endcode
The overloads are checked for existence in that order and the first that
matches will be selected. <br>
The `ctx` argument can be used either as a tag type to provide conversions
for third-party types, or to pass extra data to the conversion function.
@par Exception Safety
Strong guarantee.
@tparam T The type of the object to convert.
@tparam Context The type of context passed to the conversion function.
@param t The object to convert.
@param ctx Context passed to the conversion function.
@param jv @ref value out parameter.
@see @ref value_from_tag, @ref value_to,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
tag_invoke: A general pattern for supporting customisable functions</a>
*/
template< class T, class Context >
void
value_from(
T&& t,
Context const& ctx,
value& jv)
{
using bare_T = detail::remove_cvref<T>;
BOOST_STATIC_ASSERT(detail::conversion_round_trips<
Context, bare_T, detail::value_from_conversion>::value);
using cat = detail::value_from_category<Context, bare_T>;
detail::value_from_impl( cat(), jv, std::forward<T>(t), ctx );
}
/** Convert an object of type `T` to @ref value.
This function attempts to convert an object
of type `T` to @ref value using
@li one of @ref value's constructors,
@li a library-provided generic conversion, or
@li a user-provided overload of `tag_invoke`.
Out of the box the function supports types satisfying
<a href="https://en.cppreference.com/w/cpp/named_req/SequenceContainer"><em>SequenceContainer</em></a>,
arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`,
`std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`.
Conversion of other types is done by calling an overload of `tag_invoke`
found by argument-dependent lookup. Its signature should be similar to:
@code
template< class FullContext >
void tag_invoke( value_from_tag, value&, T, const Context& , const FullContext& );
@endcode
or
@code
void tag_invoke( value_from_tag, value&, T, const Context& );
@endcode
or
@code
void tag_invoke( value_from_tag, value&, T );
@endcode
The overloads are checked for existence in that order and the first that
matches will be selected. <br>
A @ref value constructed with the @ref storage_ptr passed to
@ref value_from is passed as the second argument to ensure that the memory
resource is correctly propagated.
@par Exception Safety
Strong guarantee.
@tparam T The type of the object to convert.
@tparam Context The type of context passed to the conversion function.
@returns `t` converted to @ref value.
@param t The object to convert.
@param ctx Context passed to the conversion function.
@param sp A storage pointer referring to the memory resource
to use for the returned @ref value. The default argument for this
parameter is `{}`.
@see @ref value_from_tag, @ref value_to,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
tag_invoke: A general pattern for supporting customisable functions</a>
*/
template< class T, class Context >
#ifndef BOOST_JSON_DOCS
typename std::enable_if<
!std::is_same< detail::remove_cvref<Context>, storage_ptr >::value &&
!std::is_same< detail::remove_cvref<Context>, value >::value,
value >::type
#else
value
#endif
value_from(
T&& t,
Context const& ctx,
storage_ptr sp = {})
{
value jv(std::move(sp));
value_from( static_cast<T&&>(t), ctx, jv );
return jv;
}
/** Convert an object of type `T` to @ref value.
This function attempts to convert an object
@@ -59,11 +215,7 @@ value_from(
T&& t,
value& jv)
{
using bare_T = detail::remove_cvref<T>;
BOOST_STATIC_ASSERT(detail::conversion_round_trips<
bare_T, detail::value_from_conversion>::value);
using cat = detail::value_from_category<bare_T>;
detail::value_from_impl( cat(), jv, std::forward<T>(t) );
value_from( static_cast<T&&>(t), detail::no_context(), jv );
}
/** Convert an object of type `T` to @ref value.
@@ -117,10 +269,8 @@ value_from(
T&& t,
storage_ptr sp = {})
{
value jv(std::move(sp));
json::value_from(
std::forward<T>(t), jv);
return jv;
return value_from(
static_cast<T&&>(t), detail::no_context(), std::move(sp) );
}
/** Determine if `T` can be converted to @ref value.

View File

@@ -37,7 +37,86 @@ namespace json {
found by argument-dependent lookup. Its signature should be similar to:
@code
T tag_invoke( value_to_tag<T>, value );
template< class FullContext >
T tag_invoke( value_to_tag<T>, const value&, const Context& , const FullContext& );
@endcode
or
@code
T tag_invoke( value_to_tag<T>, const value&, const Context& );
@endcode
or
@code
result<T> tag_invoke( value_to_tag<T>, const value& );
@endcode
The overloads are checked for existence in that order and the first that
matches will be selected. <br>
The object returned by the function call is returned by @ref value_to as
the result of the conversion. <br>
The `ctx` argument can be used either as a tag type to provide conversions
for third-party types, or to pass extra data to the conversion function.
@par Constraints
@code
! std::is_reference< T >::value
@endcode
@par Exception Safety
Strong guarantee.
@tparam T The type to convert to.
@tparam Context The type of context passed to the conversion function.
@returns `jv` converted to `result<T>`.
@param jv The @ref value to convert.
@param ctx Context passed to the conversion function.
@see @ref value_to_tag, @ref value_from,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
tag_invoke: A general pattern for supporting customisable functions</a>
*/
template< class T, class Context >
T
value_to( value const& jv, Context const& ctx )
{
BOOST_STATIC_ASSERT(! std::is_reference<T>::value);
using bare_T = detail::remove_cvref<T>;
BOOST_STATIC_ASSERT(detail::conversion_round_trips<
Context, bare_T, detail::value_to_conversion>::value);
using cat = detail::value_to_category<Context, bare_T>;
return detail::value_to_impl( cat(), value_to_tag<bare_T>(), jv, ctx );
}
/** Convert a @ref value to an object of type `T`.
This function attempts to convert a @ref value
to `T` using
@li one of @ref value's accessors, or
@li a library-provided generic conversion, or
@li a user-provided overload of `tag_invoke`.
Out of the box the function supports types satisfying
<a href="https://en.cppreference.com/w/cpp/named_req/SequenceContainer"><em>SequenceContainer</em></a>,
arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`,
`std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`.
Conversion of other types is done by calling an overload of `tag_invoke`
found by argument-dependent lookup. Its signature should be similar to:
@code
T tag_invoke( value_to_tag<T>, const value& );
@endcode
The object returned by the function call is
@@ -66,12 +145,7 @@ template<class T>
T
value_to(const value& jv)
{
BOOST_STATIC_ASSERT(! std::is_reference<T>::value);
using bare_T = detail::remove_cvref<T>;
BOOST_STATIC_ASSERT(detail::conversion_round_trips<
bare_T, detail::value_to_conversion>::value);
using cat = detail::value_to_category<bare_T>;
return detail::value_to_impl( cat(), value_to_tag<bare_T>(), jv );
return value_to<T>( jv, detail::no_context() );
}
/** Convert a @ref value to a @ref result of `T`.
@@ -94,13 +168,97 @@ value_to(const value& jv)
found by argument-dependent lookup. Its signature should be similar to:
@code
result<T> tag_invoke( try_value_to_tag<T>, value const& );
template< class FullContext >
result<T> tag_invoke( try_value_to_tag<T>, const value&, const Context& , const FullContext& );
@endcode
or
@code
result<T> tag_invoke( try_value_to_tag<T>, const value&, const Context& );
@endcode
or
@code
result<T> tag_invoke( try_value_to_tag<T>, const value& );
@endcode
The overloads are checked for existence in that order and the first that
matches will be selected. <br>
If an error occurs during conversion, the result will store the error code
associated with the error. If an exception is thrown, the function will
attempt to retrieve the associated error code and return it, otherwise it
will return `error::exception`, unless the exception type is
`std::bad_alloc`, which will be allowed to propagate. <br>
The `ctx` argument can be used either as a tag type to provide conversions
for third-party types, or to pass extra data to the conversion function.
@par Constraints
@code
! std::is_reference< T >::value
@endcode
@par Exception Safety
Strong guarantee.
@tparam T The type to convert to.
@tparam Context The type of context passed to the conversion function.
@param jv The @ref value to convert.
@param ctx Context passed to the conversion function.
@returns `jv` converted to `result<T>`.
@see @ref value_to_tag, @ref value_to, @ref value_from,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
tag_invoke: A general pattern for supporting customisable functions</a>
*/
template< class T, class Context >
typename result_for<T, value>::type
try_value_to( value const& jv, Context const& ctx )
{
BOOST_STATIC_ASSERT(! std::is_reference<T>::value);
using bare_T = detail::remove_cvref<T>;
BOOST_STATIC_ASSERT(detail::conversion_round_trips<
Context, bare_T, detail::value_to_conversion>::value);
using cat = detail::value_to_category<Context, bare_T>;
return detail::value_to_impl(
cat(), try_value_to_tag<bare_T>(), jv, ctx );
}
/** Convert a @ref value to a @ref result of `T`.
This function attempts to convert a @ref value
to `result<T>` using
@li one of @ref value's accessors, or
@li a library-provided generic conversion, or
@li a user-provided overload of `tag_invoke`.
Out of the box the function supports types satisfying
<a href="https://en.cppreference.com/w/cpp/named_req/SequenceContainer"><em>SequenceContainer</em></a>,
arrays, arithmetic types, `bool`, `std::tuple`, `std::pair`,
`std::variant`, `std::optional`, `std::monostate`, and `std::nullopt_t`.
Conversion of other types is done by calling an overload of `tag_invoke`
found by argument-dependent lookup. Its signature should be similar to:
@code
result<T> tag_invoke( try_value_to_tag<T>, const value& );
@endcode
If an error occurs during conversion, the result will store the error code
associated with the error. If an exception is thrown, the function will
attempt to retrieve the associated error code and return it, otherwise it
will return `error::exception`.
will return `error::exception`, unless the exception type is
`std::bad_alloc`, which will be allowed to propagate.
@par Constraints
@code
@@ -124,13 +282,7 @@ template<class T>
typename result_for<T, value>::type
try_value_to(const value& jv)
{
BOOST_STATIC_ASSERT(! std::is_reference<T>::value);
using bare_T = detail::remove_cvref<T>;
BOOST_STATIC_ASSERT(detail::conversion_round_trips<
bare_T, detail::value_to_conversion>::value);
using cat = detail::value_to_category<bare_T>;
return detail::value_to_impl(
cat(), try_value_to_tag<bare_T>(), jv );
return try_value_to<T>( jv, detail::no_context() );
}
/** Convert a @ref value to an object of type `T`.

View File

@@ -60,7 +60,10 @@ project : requirements <include>. ;
for local f in $(SOURCES)
{
run $(f) main.cpp /boost/json//boost_json ;
run $(f) main.cpp /boost/json//boost_json
: requirements
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
;
}
run limits.cpp main.cpp /boost/json//json_sources

View File

@@ -32,6 +32,31 @@ try_value_to( const value& jv );
}
//]
//[doc_forward_conversion_3
namespace boost {
namespace json {
class value;
struct value_from_tag;
template< class T >
struct try_value_to_tag;
template< class T1, class T2 >
struct result_for;
template< class T, class Context >
void value_from( T&& t, value& jv, const Context& ctx );
template< class T, class Context >
typename result_for< T, value >::type
try_value_to( const value& jv, const Context& ctx );
}
}
//]
#include "doc_types.hpp"
#include <system_error>
@@ -41,7 +66,8 @@ namespace user_ns
{
template< class JsonValue >
void tag_invoke( const boost::json::value_from_tag&, JsonValue& jv, const ip_address& addr )
void tag_invoke(
const boost::json::value_from_tag&, JsonValue& jv, const ip_address& addr )
{
const unsigned char* b = addr.begin();
jv = { b[0], b[1], b[2], b[3] };
@@ -84,6 +110,50 @@ tag_invoke(
}
//]
//[doc_forward_conversion_4
namespace user_ns
{
struct as_string
{ };
template< class JsonValue >
void
tag_invoke(
const boost::json::value_from_tag&, JsonValue& jv,
const ip_address& addr,
const as_string& )
{
auto& js = jv.emplace_string();
js.resize( 4 * 3 + 3 + 1 ); // XXX.XXX.XXX.XXX\0
auto it = addr.begin();
auto n = std::sprintf(
js.data(), "%hhu.%hhu.%hhu.%hhu", it[0], it[1], it[2], it[3] );
js.resize(n);
}
template< class JsonValue >
typename boost::json::result_for< ip_address, JsonValue >::type
tag_invoke(
const boost::json::try_value_to_tag< ip_address >&,
const JsonValue& jv,
const as_string& )
{
const auto* js = jv.if_string();
if( ! js )
return make_error_code( std::errc::invalid_argument );
unsigned char octets[4];
int result = std::sscanf(
js->data(), "%hhu.%hhu.%hhu.%hhu", octets, octets + 1, octets + 2, octets + 3 );
if( result != 4 )
return make_error_code( std::errc::invalid_argument );
return ip_address( octets[0], octets[1], octets[2], octets[3] );
}
}
//]
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
@@ -100,7 +170,7 @@ public:
void
run()
{
value const jv{ 212, 115, 81, 22 };
value jv{ 212, 115, 81, 22 };
auto const addr = value_to< user_ns::ip_address >( jv );
BOOST_TEST( get<0>(addr) == 212 );
BOOST_TEST( get<1>(addr) == 115 );
@@ -109,6 +179,19 @@ public:
value const jv2 = value_from( addr );
BOOST_TEST( jv == jv2 );
jv = value_from( addr, user_ns::as_string() );
BOOST_TEST( jv.is_string() );
string const& js = jv.get_string();
BOOST_TEST( js == "212.115.81.22" );
auto const addr2 = value_to< user_ns::ip_address >(
jv, user_ns::as_string() );
BOOST_TEST( get<0>(addr2) == 212 );
BOOST_TEST( get<1>(addr2) == 115 );
BOOST_TEST( get<2>(addr2) == 81 );
BOOST_TEST( get<3>(addr2) == 22 );
}
};

View File

@@ -98,6 +98,17 @@ get(ip_address const& addr )
return *(addr.begin() + N);
}
inline
bool
operator==(ip_address const& l, ip_address const& r) noexcept
{
return
get<0>(l) == get<0>(r) &&
get<1>(l) == get<1>(r) &&
get<2>(l) == get<2>(r) &&
get<3>(l) == get<3>(r);
}
} // namespace user_ns
#endif // BOOST_JSON_DOC_DOC_TYPES_HPP

View File

@@ -14,7 +14,6 @@
#elif defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunused"
# pragma clang diagnostic ignored "-Wmismatched-tags"
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused"
@@ -111,6 +110,147 @@ tag_invoke( const try_value_to_tag< ip_address >&, value const& jv )
} // namespace user_ns
//[doc_context_conversion_1
namespace user_ns
{
struct as_string
{ };
void
tag_invoke(
boost::json::value_from_tag, boost::json::value& jv, const ip_address& addr, const as_string& )
{
boost::json::string& js = jv.emplace_string();
js.resize( 4 * 3 + 3 + 1 ); // XXX.XXX.XXX.XXX\0
auto it = addr.begin();
auto n = std::sprintf(
js.data(), "%hhu.%hhu.%hhu.%hhu", it[0], it[1], it[2], it[3] );
js.resize(n);
}
ip_address
tag_invoke(
boost::json::value_to_tag<ip_address>, const boost::json::value& jv, const as_string& )
{
const boost::json::string& js = jv.as_string();
unsigned char octets[4];
int result = std::sscanf(
js.data(), "%hhu.%hhu.%hhu.%hhu", octets, octets + 1, octets + 2, octets + 3 );
if( result != 4 )
throw std::invalid_argument("not an IP address");
return ip_address( octets[0], octets[1], octets[2], octets[3] );
}
}
//]
//[doc_context_conversion_4
namespace user_ns
{
struct as_iso_8601
{ };
void
tag_invoke(
boost::json::value_from_tag, boost::json::value& jv, std::chrono::system_clock::time_point tp, const as_iso_8601& )
{
boost::json::string& js = jv.emplace_string();
js.resize( 4 + 2 * 5 + 5 + 1 ); // YYYY-mm-ddTHH:MM:ss\0
std::time_t t = std::chrono::system_clock::to_time_t( tp );
std::tm tm = *std::gmtime( &t );
std::size_t n = std::strftime(
js.data(), js.size(), "%FT%T", &tm );
js.resize(n);
}
}
//]
//[doc_context_conversion_6
namespace user_ns
{
struct date_format
{
std::string format;
std::size_t buffer_size;
};
void
tag_invoke(
boost::json::value_from_tag, boost::json::value& jv, std::chrono::system_clock::time_point tp, const date_format& ctx )
{
boost::json::string& js = jv.emplace_string();
js.resize( ctx.buffer_size );
std::time_t t = std::chrono::system_clock::to_time_t( tp );
std::size_t n = std::strftime(
js.data(), js.size(), ctx.format.c_str(), std::gmtime( &t ) );
js.resize(n);
}
}
//]
//[doc_context_conversion_10
namespace user_ns
{
struct maps_as_objects
{ };
template<
class Key,
class Value,
class Ctx >
void
tag_invoke(
boost::json::value_from_tag,
boost::json::value& jv,
const std::map<Key, Value>& m,
const maps_as_objects&,
const Ctx& ctx )
{
boost::json::object& jo = jv.emplace_object();
for( const auto& item: m )
{
auto k = boost::json::value_from( item.first, ctx, jo.storage() );
auto v = boost::json::value_from( item.second, ctx, jo.storage() );
jo[std::move( k.as_string() )] = std::move( v );
}
}
template<
class Key,
class Value,
class Ctx >
std::map<Key, Value>
tag_invoke(
boost::json::value_to_tag< std::map<Key, Value> >,
boost::json::value const& jv,
const maps_as_objects&,
const Ctx& ctx )
{
const boost::json::object& jo = jv.as_object();
std::map< Key, Value > result;
for( auto&& item: jo )
{
Key k = boost::json::value_to< Key >( item.key(), ctx );
Value v = boost::json::value_to< Value >( item.value(), ctx );
result.emplace( std::move(k), std::move(v) );
}
return result;
}
}
//]
namespace boost {
namespace json {
@@ -874,6 +1014,114 @@ usingSpecializedTrait()
assert( jv1 == jv2 );
}
void
usingContextualConversions()
{
using namespace user_ns;
using namespace boost::json;
{
//[doc_context_conversion_2
ip_address addr( 192, 168, 10, 11 );
value jv = value_from( addr, as_string() );
assert( jv == parse(R"( "192.168.10.11" )") );
ip_address addr2 = value_to< ip_address >( jv, as_string() );
assert(std::equal(
addr.begin(), addr.end(), addr2.begin() ));
//]
(void)addr2;
}
{
//[doc_context_conversion_3
std::map< std::string, ip_address > computers = {
{ "Alex", { 192, 168, 1, 1 } },
{ "Blake", { 192, 168, 1, 2 } },
{ "Carol", { 192, 168, 1, 3 } },
};
value jv = value_from( computers, as_string() );
assert( jv == parse(
"{ "
" \"Alex\" : \"192.168.1.1\", "
" \"Blake\": \"192.168.1.2\", "
" \"Carol\": \"192.168.1.3\" "
"} "
) );
//]
(void)jv;
}
{
//[doc_context_conversion_5
std::chrono::system_clock::time_point tp;
value jv = value_from( tp, as_iso_8601() );
assert( jv == parse(R"( "1970-01-01T00:00:00" )") );
//]
(void)jv;
}
{
//[doc_context_conversion_7
std::chrono::system_clock::time_point tp;
value jv = value_from( tp, date_format{ "%T %D", 18 } );
assert( jv == parse(R"( "00:00:00 01/01/70" )") );
jv = value_from( tp, as_iso_8601() );
assert( jv == parse(R"( "1970-01-01T00:00:00" )") );
//]
(void)jv;
}
{
//[doc_context_conversion_8
using time_point = std::chrono::system_clock::time_point;
time_point start;
std::vector< std::pair<time_point, ip_address> > log = {
{ start += std::chrono::seconds(10), {192, 168, 10, 11} },
{ start += std::chrono::hours(2), {192, 168, 10, 13} },
{ start += std::chrono::minutes(14), {192, 168, 10, 10} },
};
value jv = value_from(
log, std::make_tuple( as_string(), as_iso_8601() ) );
assert( jv == parse(
" [ "
" [ \"1970-01-01T00:00:10\", \"192.168.10.11\" ], "
" [ \"1970-01-01T02:00:10\", \"192.168.10.13\" ], "
" [ \"1970-01-01T02:14:10\", \"192.168.10.10\" ] "
" ] "
) );
//]
(void)jv;
}
{
using time_point = std::chrono::system_clock::time_point;
time_point start;
//[doc_context_conversion_9
std::map< time_point, ip_address > log = {
{ start += std::chrono::seconds(10), {192, 168, 10, 11} },
{ start += std::chrono::hours(2), {192, 168, 10, 13} },
{ start += std::chrono::minutes(14), {192, 168, 10, 10} },
};
value jv = value_from(
log,
std::make_tuple( maps_as_objects(), as_string(), as_iso_8601() ) );
assert( jv == parse(
" { "
" \"1970-01-01T00:00:10\": \"192.168.10.11\", "
" \"1970-01-01T02:00:10\": \"192.168.10.13\", "
" \"1970-01-01T02:14:10\": \"192.168.10.10\" "
" } "
) );
//]
(void)jv;
}
}
} // namespace
class snippets_test
@@ -891,6 +1139,7 @@ public:
usingPointer();
usingSpecializedTrait();
usingSetAtPointer();
usingContextualConversions();
BOOST_TEST_PASS();
}

View File

@@ -13,6 +13,7 @@
#include <boost/json/value.hpp> // prevent intellisense bugs
#include <boost/json/serialize.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/describe/class.hpp>
#include <boost/describe/enum.hpp>
@@ -185,6 +186,64 @@ BOOST_DESCRIBE_STRUCT(T11, (T10), (s))
BOOST_DEFINE_ENUM_CLASS(E1, a, b, c)
//----------------------------------------------------------
struct custom_context
{ };
struct T12
{ };
void
tag_invoke(
::boost::json::value_from_tag,
::boost::json::value& jv,
T12 const&,
custom_context const& )
{
jv = "T12";
}
//----------------------------------------------------------
struct another_context
{ };
struct T13
{ };
void
tag_invoke(
::boost::json::value_from_tag,
::boost::json::value& jv,
T13 const&,
another_context const& )
{
jv = "T13";
}
//----------------------------------------------------------
template< class T, class Ctx >
void
tag_invoke(
::boost::json::value_from_tag,
::boost::json::value& jv,
std::vector< T > const& vec,
another_context const&,
Ctx const& ctx )
{
boost::json::object& jo = jv.emplace_object();
unsigned i = 0;
for( auto&& item: vec )
{
jo.emplace(
std::to_string(++i),
boost::json::value_from( item, ctx, jo.storage() ) );
}
}
} // namespace value_from_test_ns
template<class T>
@@ -225,20 +284,13 @@ struct is_described_class<::value_from_test_ns::T11>
namespace {
template<class T>
template< class T, class... Context >
static
void
testValueCtor(T const& t)
testValueCtor( T const& t, Context const& ... ctx )
{
BOOST_TEST( serialize(value_from(t)) == serialize(value(t)) );
}
template<class T>
static
void
testValueCtor()
{
testValueCtor(T{});
BOOST_TEST(
serialize(value_from( t, ctx... )) == serialize(value(t)) );
}
} // namespace
@@ -269,33 +321,35 @@ BOOST_STATIC_ASSERT(has_value_from<std::map<string_view, int>>::value);
class value_from_test
{
public:
template< class... Context >
static
void
testValueCtors()
testValueCtors( Context const& ... ctx )
{
// value_from supports every value constructor
testValueCtor<value>();
testValueCtor<value, Context...>( value(), ctx... );
char const* s = "5";
testValueCtor(s);
testValueCtor<char const*, Context...>( s, ctx... );
}
template< class... Context >
static
void
testGeneral()
testGeneral( Context const& ... ctx )
{
{
int a[4] = {1, 2, 3, 4};
value b{1, 2, 3, 4};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_array());
BOOST_TEST(serialize(c) == serialize(b));
}
{
std::tuple<int, string, int, bool> a{1, "2", 42, true};
value b{1, "2", 42, true};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_array());
BOOST_TEST(serialize(c) == serialize(b));
}
@@ -307,12 +361,12 @@ public:
array& b_arr = b.emplace_array();
b_arr.insert(b_arr.end(), a.begin(), a.end());
BOOST_TEST(value_from(a) == b);
BOOST_TEST(value_from( a, ctx... ) == b);
}
{
std::pair<int, string> a{1, string("2")};
value b{1, "2"};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_array());
BOOST_TEST(serialize(c) == serialize(b));
}
@@ -320,33 +374,34 @@ public:
// ensures that this isn't parsed as a key value pair
std::pair<string_view, int> a{"2", 1};
value b{"2", 1};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_array());
BOOST_TEST(serialize(c) == serialize(b));
}
{
key_value_pair a{"2", 1};
value b{"2", 1};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_array());
BOOST_TEST(serialize(c) == serialize(b));
}
{
::value_from_test_ns::T7 a;
value b = value_from(a);
value b = value_from( a, ctx... );
BOOST_TEST(b.is_null());
}
}
template< class... Context >
static
void
testAssociative()
testAssociative( Context const& ... ctx )
{
{
std::map<string_view, int> a =
{{"a", 1}, {"b", 2}, {"c", 3}};
value b = {{"a", 1}, {"b", 2}, {"c", 3}};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_object());
BOOST_TEST(a.size() == c.as_object().size());
BOOST_TEST(b.as_object().size() == c.as_object().size());
@@ -355,7 +410,7 @@ public:
std::unordered_map<std::string, int> a =
{{"a", 1}, {"b", 2}, {"c", 3}};
value b = {{"a", 1}, {"b", 2}, {"c", 3}};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(c.is_object());
BOOST_TEST(a.size() == c.as_object().size());
BOOST_TEST(b.as_object().size() == c.as_object().size());
@@ -364,7 +419,7 @@ public:
std::map<int, int> a =
{{1, 1}, {2, 2}, {3, 3}};
value b = {{1, 1}, {2, 2}, {3, 3}};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(!c.is_object());
BOOST_TEST(a.size() == c.as_array().size());
BOOST_TEST(b.as_array().size() == c.as_array().size());
@@ -373,19 +428,20 @@ public:
std::unordered_map<int, int> a =
{{1, 1}, {2, 2}, {3, 3}};
value b = {{1, 1}, {2, 2}, {3, 3}};
value c = value_from(a);
value c = value_from( a, ctx... );
BOOST_TEST(!c.is_object());
BOOST_TEST(a.size() == c.as_array().size());
BOOST_TEST(b.as_array().size() == c.as_array().size());
}
}
template< class... Context >
static
void
testPreferUserCustomizations()
testPreferUserCustomizations( Context const& ... ctx )
{
value_from_test_ns::T5 t5;
BOOST_TEST((::boost::json::value_from(t5) == "T5"));
BOOST_TEST((::boost::json::value_from( t5, ctx... ) == "T5"));
}
void
@@ -413,62 +469,137 @@ public:
}
}
template< class... Context >
static
void
testDescribed()
testDescribed( Context const& ... ctx )
{
ignore_unused( ctx... );
#ifdef BOOST_DESCRIBE_CXX14
::value_from_test_ns::T10 t10{909, -1.45};
value jv = value_from(t10);
value jv = value_from( t10, ctx... );
BOOST_TEST(( jv == value{{"n", 909}, {"d", -1.45}} ));
::value_from_test_ns::T11 t11;
t11.n = 67;
t11.d = -.12;
t11.s = "qwerty";
jv = value_from(t11);
jv = value_from( t11, ctx... );
BOOST_TEST(( jv == value{{"n", 67}, {"d", -.12}, {"s", "qwerty"}} ));
::value_from_test_ns::E1 e1 = ::value_from_test_ns::E1::a;
BOOST_TEST( value_from(e1) == "a" );
BOOST_TEST( value_from( e1, ctx... ) == "a" );
e1 = ::value_from_test_ns::E1::b;
BOOST_TEST( value_from(e1) == "b" );
BOOST_TEST( value_from( e1, ctx... ) == "b" );
e1 = static_cast<::value_from_test_ns::E1>(1001);
BOOST_TEST( value_from(e1) == 1001 );
BOOST_TEST( value_from( e1, ctx... ) == 1001 );
#endif
}
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
void testOptional()
template< class... Context >
static
void testOptional( Context const& ... ctx )
{
ignore_unused( ctx... );
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
std::vector<std::optional<int>> opts{1, 2, 3, {}, 5};
value jv = value_from(opts);
value jv = value_from( opts, ctx... );
BOOST_TEST( jv == (value{1, 2, 3, nullptr, 5}) );
BOOST_TEST( value_from(std::nullopt).is_null() );
}
BOOST_TEST( value_from( std::nullopt, ctx... ).is_null() );
#endif
}
#ifndef BOOST_NO_CXX17_HDR_VARIANT
template< class... Context >
static
void
testVariant()
testVariant( Context const& ... ctx )
{
ignore_unused( ctx... );
#ifndef BOOST_NO_CXX17_HDR_VARIANT
std::variant<int, ::value_from_test_ns::T5, double> v = 4;
value jv = value_from(v);
value jv = value_from( v, ctx... );
BOOST_TEST(jv == 4);
v = 0.5;
jv = value_from(v);
jv = value_from( v, ctx... );
BOOST_TEST(jv == 0.5);
v = ::value_from_test_ns::T5{};
jv = value_from(v);
jv = value_from( v, ctx... );
BOOST_TEST(jv == "T5");
BOOST_TEST( value() == value_from(std::monostate()) );
}
BOOST_TEST( value() == value_from( std::monostate(), ctx... ) );
#endif // BOOST_NO_CXX17_HDR_VARIANT
}
void
testContext()
{
value jv = value_from(
value_from_test_ns::T1{}, value_from_test_ns::custom_context() );
BOOST_TEST( jv == 42 );
jv = value_from(
value_from_test_ns::T12{},
value_from_test_ns::custom_context() );
BOOST_TEST( jv == "T12" );
jv = value_from(
value_from_test_ns::T13{},
std::make_tuple(
value_from_test_ns::custom_context(),
value_from_test_ns::another_context() ) );
BOOST_TEST( jv == "T13" );
jv = value_from(
std::pair<value_from_test_ns::T12, value_from_test_ns::T13>(),
std::make_tuple(
value_from_test_ns::custom_context(),
value_from_test_ns::another_context() ) );
BOOST_TEST( jv == (array{ "T12", "T13" }) );
auto ctx = std::make_tuple(
value_from_test_ns::custom_context(),
value_from_test_ns::another_context() );
using Ctx = decltype(ctx);
using Sup = detail::supported_context<
Ctx,
std::vector<value_from_test_ns::T12>,
detail::value_from_conversion >;
BOOST_STATIC_ASSERT( Sup::index::value == 1 );
BOOST_STATIC_ASSERT(
std::is_same<
Sup::type,
value_from_test_ns::another_context>::value );
jv = value_from(
std::vector<value_from_test_ns::T12>(2),
std::make_tuple(
value_from_test_ns::custom_context(),
value_from_test_ns::another_context() ) );
BOOST_TEST( jv == (object{ {"1", "T12"}, {"2", "T12"} }) );
}
struct run_templated_tests
{
// this overload supports zero or one default constructible contexts
// and used with mp_for_each
template< class... Context >
void operator()( mp11::mp_list< Context... > )
{
testValueCtors( Context()... );
testGeneral( Context()... );
testAssociative( Context()... );
testPreferUserCustomizations( Context()... );
testDescribed( Context()... );
testOptional( Context()... );
testVariant( Context()... );
}
};
void
run()
@@ -476,18 +607,24 @@ public:
check("42", ::value_from_test_ns::T1{});
check("[[1,2,3,4],\"test\"]", ::value_from_test_ns::T2{});
testValueCtors();
testGeneral();
testAssociative();
testPreferUserCustomizations();
mp11::mp_for_each<
mp11::mp_list<
mp11::mp_list<>,
mp11::mp_list<detail::no_context>,
mp11::mp_list<value_from_test_ns::custom_context>,
mp11::mp_list<
std::tuple<value_from_test_ns::custom_context>>,
mp11::mp_list<
std::tuple<
std::tuple<value_from_test_ns::custom_context>>>,
mp11::mp_list<
std::tuple<
detail::no_context,
value_from_test_ns::custom_context>>
>>( run_templated_tests() );
testContext();
testTrySize();
testDescribed();
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
testOptional();
#endif
#ifndef BOOST_NO_CXX17_HDR_VARIANT
testVariant();
#endif // BOOST_NO_CXX17_HDR_VARIANT
}
};

View File

@@ -11,6 +11,7 @@
#include <boost/json/value_to.hpp>
#include <boost/json/value_from.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/describe/class.hpp>
#include <boost/describe/enum.hpp>
@@ -124,6 +125,26 @@ struct T8
};
BOOST_DESCRIBE_STRUCT(T8, (), (n, d, opt_s))
//----------------------------------------------------------
struct custom_context
{ };
struct T9
{ };
boost::json::result<T9>
tag_invoke(
boost::json::try_value_to_tag<T9>,
boost::json::value const& jv,
custom_context const&)
{
boost::json::string const* str = jv.if_string();
if( str && *str == "T9" )
return T9{};
return make_error_code(boost::json::error::syntax);
}
} // namespace value_to_test_ns
namespace std
@@ -173,119 +194,137 @@ class value_to_test
{
public:
template<class T>
#define BOOST_TEST_CONV(x, ... ) \
BOOST_TEST( value_to<decltype(x)>( value_from((x)), __VA_ARGS__ ) == (x) )
template< class... Context >
static
void
check(T t)
testNumberCast( Context const& ... ctx )
{
BOOST_TEST(value_to<T>(value_from(t)) == t);
BOOST_TEST_CONV( (short)-1, ctx... );
BOOST_TEST_CONV( (int)-2, ctx... );
BOOST_TEST_CONV( (long)-3, ctx... );
BOOST_TEST_CONV( (long long)-4, ctx... );
BOOST_TEST_CONV( (unsigned short)1, ctx... );
BOOST_TEST_CONV( (unsigned int)2, ctx... );
BOOST_TEST_CONV( (unsigned long)3, ctx... );
BOOST_TEST_CONV( (unsigned long long)4, ctx... );
BOOST_TEST_CONV( (float)1.5, ctx... );
BOOST_TEST_CONV( (double)2.5, ctx... );
BOOST_TEST_CONV( true, ctx... );
BOOST_TEST_THROWS_WITH_LOCATION(value_to<bool>(
value(), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(value_to<int>(
value(), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(value_to<double>(
value(), ctx... ));
}
template< class... Context >
static
void
testNumberCast()
testJsonTypes( Context const& ... ctx )
{
check((short)-1);
check((int)-2);
check((long)-3);
check((long long)-4);
check((unsigned short)1);
check((unsigned int)2);
check((unsigned long)3);
check((unsigned long long)4);
check((float)1.5);
check((double)2.5);
check(true);
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<bool>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<int>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<double>(value()) );
value_to<object>( value(object_kind), ctx... );
value_to<array>( value(array_kind), ctx... );
value_to<string>( value(string_kind), ctx... );
BOOST_TEST_THROWS_WITH_LOCATION(value_to<object>(
value(), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(value_to<array>(
value(), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(value_to<string>(
value(), ctx... ));
}
template< class... Context >
static
void
testJsonTypes()
testGenerics( Context const& ... ctx )
{
value_to<object>(value(object_kind));
value_to<array>(value(array_kind));
value_to<string>(value(string_kind));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<object>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<array>(value()) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<string>(value()) );
}
void
testGenerics()
{
check(std::string("test"));
check(std::map<std::string, int>
{
{"a", 1}, {"b", 2}, {"c", 3}
});
check(std::multimap<std::string, int>
{
{"2", 4}, {"3", 9}, {"5", 25}
});
check(std::unordered_map<std::string, int>
{
{ "a", 1 }, {"b", 2}, {"c", 3}
});
check(std::vector<int>{1, 2, 3, 4});
check(std::vector<bool>{true, false, false, true});
check(std::make_pair(std::string("test"), 5));
check(std::make_tuple(std::string("outer"),
std::make_pair(std::string("test"), 5)));
check(std::map<int, int>
{
{2, 4}, {3, 9}, {5, 25}
});
BOOST_TEST_CONV( std::string("test"), ctx... );
BOOST_TEST_CONV(
(std::map<std::string, int>
{
{"a", 1}, {"b", 2}, {"c", 3}
}),
ctx... );
BOOST_TEST_CONV(
(std::multimap<std::string, int>
{
{"2", 4}, {"3", 9}, {"5", 25}
}),
ctx... );
BOOST_TEST_CONV(
(std::unordered_map<std::string, int>
{
{ "a", 1 }, {"b", 2}, {"c", 3}
}),
ctx... );
BOOST_TEST_CONV( (std::vector<int>{1, 2, 3, 4}), ctx... );
BOOST_TEST_CONV(
(std::vector<bool>{true, false, false, true}), ctx... );
BOOST_TEST_CONV(
std::make_pair(std::string("test"), 5), ctx... );
BOOST_TEST_CONV(
std::make_tuple(
std::string("outer"), std::make_pair(std::string("test"), 5) ),
ctx... );
BOOST_TEST_CONV(
(std::map<int, int>
{
{2, 4}, {3, 9}, {5, 25}
}),
ctx... );
{
std::array<int, 1000> arr;
arr.fill(0);
check(arr);
BOOST_TEST_CONV( arr, ctx... );
}
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::string>(value()) );
value_to<std::string>( value(), ctx... ));
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::map<std::string, int>>(value()) ));
(value_to<std::map<std::string, int>>( value(), ctx... )));
// element fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::map<std::string, int>>(
value{{"1", 1}, {"2", true}, {"3", false}}) ));
(value_to<std::map<std::string, int>>(
value{{"1", 1}, {"2", true}, {"3", false}}, ctx... )));
// reserve fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<value_to_test_ns::T4>(
value{{"1", 1}, {"2", true}, {"3", false}}) ));
(value_to<value_to_test_ns::T4>(
value{{"1", 1}, {"2", true}, {"3", false}}, ctx... )));
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::vector<int>>(value()) );
value_to<std::vector<int>>( value(), ctx... ));
// element fails
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::vector<int>>(value{1, 2, false, 3}) );
value_to<std::vector<int>>( value{1, 2, false, 3}, ctx... ));
// reserve fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::array<int, 4>>(value{1, 2, 3}) ));
(value_to<std::array<int, 4>>( value{1, 2, 3}, ctx... )));
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::array<int, 4>>(value{1, 2, 3, 4, 5}) ));
(value_to<std::array<int, 4>>(
value{1, 2, 3, 4, 5}, ctx... )));
// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::tuple<int, int, int, int>>(value()) ));
(value_to<std::tuple<int, int, int, int>>(
value(), ctx... )));
// element fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::tuple<int, int, int>>(value{1, 2, false}) ));
(value_to<std::tuple<int, int, int>>(
value{1, 2, false}, ctx... )));
// reserve fails
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::tuple<int, int, int, int>>(value{1, 2, 3}) ));
(value_to<std::tuple<int, int, int, int>>(
value{1, 2, 3}, ctx... )));
}
@@ -314,22 +353,28 @@ public:
}
}
void testNullptr()
template< class... Context >
static
void testNullptr( Context const& ... ctx )
{
(void)value_to<std::nullptr_t>(value());
(void)value_to<::value_to_test_ns::T1>(value());
(void)value_to<std::nullptr_t>( value(), ctx... );
(void)value_to<::value_to_test_ns::T1>( value(), ctx... );
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<std::nullptr_t>(value(1)) ));
value_to<std::nullptr_t>( value(1), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(
( value_to<::value_to_test_ns::T1>(value(1)) ));
value_to<::value_to_test_ns::T1>( value(1), ctx... ));
}
void testDescribed()
template< class... Context >
static
void testDescribed( Context const& ... ctx )
{
ignore_unused( ctx... );
#ifdef BOOST_DESCRIBE_CXX14
{
value jv = {{"n", -78}, {"d", 0.125}};
auto res = try_value_to<::value_to_test_ns::T6>(jv);
auto res = try_value_to<::value_to_test_ns::T6>(
jv, ctx... );
BOOST_TEST( res );
BOOST_TEST( res->n == -78 );
BOOST_TEST( res->d == 0.125 );
@@ -340,7 +385,8 @@ public:
}
{
value jv = {{"n", 1}, {"d", 2}, {"s", "xyz"}};
auto res = try_value_to<::value_to_test_ns::T7>(jv);
auto res = try_value_to<::value_to_test_ns::T7>(
jv, ctx... );
BOOST_TEST( res );
BOOST_TEST( res->n == 1 );
BOOST_TEST( res->d == 2 );
@@ -348,31 +394,33 @@ public:
}
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( value(1) ));
value_to<::value_to_test_ns::T6>( value(1), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( value{{"x", 0}} ));
value_to<::value_to_test_ns::T6>( value{{"x", 0}}, ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T6>( (value{{"n", 0}, {"x", 0}}) ));
value_to<::value_to_test_ns::T6>(
value{{"n", 0}, {"x", 0}}, ctx... ));
{
value jv = "a";
auto e1 = value_to<::value_to_test_ns::E1>(jv);
auto e1 = value_to<::value_to_test_ns::E1>( jv, ctx... );
BOOST_TEST( e1 == ::value_to_test_ns::E1::a );
jv = "b";
e1 = value_to<::value_to_test_ns::E1>(jv);
e1 = value_to<::value_to_test_ns::E1>( jv, ctx... );
BOOST_TEST( e1 == ::value_to_test_ns::E1::b );
}
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::E1>( value(1) ));
value_to<::value_to_test_ns::E1>( value(1), ctx... ));
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::E1>( value("x") ));
value_to<::value_to_test_ns::E1>( value("x"), ctx... ));
{
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
value jv = {{"n", -78}, {"d", 0.125}};
auto res = try_value_to<::value_to_test_ns::T8>(jv);
auto res = try_value_to<::value_to_test_ns::T8>(
jv, ctx... );
BOOST_TEST( res );
BOOST_TEST( res->n == -78 );
BOOST_TEST( res->d == 0.125 );
@@ -380,82 +428,109 @@ public:
jv.as_object()["x"] = 0;
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<::value_to_test_ns::T8>( jv ));
value_to<::value_to_test_ns::T8>( jv, ctx... ));
#endif // BOOST_NO_CXX17_HDR_OPTIONAL
}
#endif // BOOST_DESCRIBE_CXX14
}
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
void testOptional()
template< class... Context >
static
void testOptional( Context const& ... ctx )
{
ignore_unused( ctx... );
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
using Opts = std::vector<std::optional<int>>;
value jv = value{1, nullptr, 3, nullptr, 5};
auto opts = value_to<Opts>(jv);
auto opts = value_to<Opts>( jv, ctx... );
BOOST_TEST( opts == (Opts{1, {}, 3, {}, 5}) );
value_to< std::nullopt_t >(value());
BOOST_TEST_THROWS_WITH_LOCATION( value_to< std::nullopt_t >(jv) );
}
BOOST_TEST_THROWS_WITH_LOCATION(
value_to< std::nullopt_t >( jv, ctx... ));
#endif
}
#ifndef BOOST_NO_CXX17_HDR_VARIANT
template< class... Context >
static
void
testVariant()
testVariant( Context const& ... ctx )
{
ignore_unused( ctx... );
#ifndef BOOST_NO_CXX17_HDR_VARIANT
using Var = std::variant<int, ::value_to_test_ns::T2, std::string>;
value jv(4);
auto v = value_to<Var>(jv);
auto v = value_to<Var>( jv, ctx... );
BOOST_TEST( v.index() == 0 );
BOOST_TEST( std::get<0>(v) == 4 );
jv = "foobar";
v = value_to<Var>(jv);
v = value_to<Var>( jv, ctx... );
BOOST_TEST( v.index() == 2 );
BOOST_TEST( std::get<2>(v) == "foobar" );
jv = "T2";
v = value_to<Var>(jv);
v = value_to<Var>( jv, ctx... );
BOOST_TEST( v.index() == 1 );
jv = 3.5;
BOOST_TEST_THROWS_WITH_LOCATION( value_to<Var>(jv) );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<Var>( jv, ctx... ));
value_to<std::monostate>( value() );
BOOST_TEST_THROWS_WITH_LOCATION( value_to<std::monostate>(jv) );
}
value_to<std::monostate>( value(), ctx... );
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::monostate>( jv, ctx... ));
#endif // BOOST_NO_CXX17_HDR_VARIANT
}
template< class... Context >
static
void
testNonThrowing()
testNonThrowing( Context const& ... ctx )
{
// using result
{
auto res = try_value_to<::value_to_test_ns::T2>(value());
// clang 3.8 seems to have some bug when dealing with a lot of
// template instantiations; this assert magically makes the problem
// go away, I assume, by instantiating the needed types beforehand
BOOST_STATIC_ASSERT(
detail::conversion_round_trips<
mp11::mp_first<
mp11::mp_list<
Context..., int> >,
::value_to_test_ns::T2,
detail::value_to_conversion>::value );
auto res = try_value_to<::value_to_test_ns::T2>(
value(), ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::syntax );
res = try_value_to<::value_to_test_ns::T2>(value("T2"));
res = try_value_to<::value_to_test_ns::T2>(
value("T2"), ctx... );
BOOST_TEST( res.has_value() );
}
// throwing overload falls back to nonthrowing customization
{
BOOST_TEST_THROWS(
value_to<::value_to_test_ns::T2>(value()),
value_to<::value_to_test_ns::T2>( value(), ctx... ),
system_error);
}
// nonthrowing overload falls back to throwing customization
{
auto res = try_value_to<::value_to_test_ns::T3>(value());
auto res = try_value_to<::value_to_test_ns::T3>(
value(), ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::not_string );
res = try_value_to<::value_to_test_ns::T3>(value(""));
res = try_value_to<::value_to_test_ns::T3>(
value(""), ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::exception );
res = try_value_to<::value_to_test_ns::T3>(value("T3"));
res = try_value_to<::value_to_test_ns::T3>(
value("T3"), ctx... );
BOOST_TEST( res.has_value() );
}
// sequence
@@ -463,7 +538,7 @@ public:
// wrong input type
{
auto res = try_value_to< std::vector<::value_to_test_ns::T2> >(
value("not an array"));
value("not an array"), ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::not_array );
@@ -472,7 +547,7 @@ public:
// wrong input type
{
auto res = try_value_to< std::array<int, 4> >(
value{1, 2});
value{1, 2}, ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::size_mismatch );
@@ -481,13 +556,13 @@ public:
// element error
{
auto res = try_value_to< std::vector<::value_to_test_ns::T2> >(
value{"T2", "T2", nullptr});
value{"T2", "T2", nullptr}, ctx... );
BOOST_TEST( res.error() == error::syntax );
}
// success
auto res = try_value_to< std::vector<::value_to_test_ns::T2> >(
value{"T2", "T2", "T2"});
value{"T2", "T2", "T2"}, ctx... );
BOOST_TEST( res.has_value() );
BOOST_TEST( res->size() == 3 );
}
@@ -496,8 +571,8 @@ public:
// wrong input type
{
auto res = try_value_to<
std::map<std::string, ::value_to_test_ns::T2> >(
value("not a map"));
std::map<std::string, ::value_to_test_ns::T2> >(
value("not a map"), ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::not_object );
@@ -506,7 +581,7 @@ public:
// reserve fails
{
auto res = try_value_to< ::value_to_test_ns::T4 >(
value{{"1", 1}, {"2", 2}, {"3", 3}});
value{{"1", 1}, {"2", 2}, {"3", 3}}, ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::size_mismatch );
@@ -515,16 +590,17 @@ public:
// element error
{
auto res = try_value_to<
std::map<std::string, ::value_to_test_ns::T2> >(
value{{"1", "T2"}, {"2", "T2"}, {"3", nullptr}});
std::map<std::string, ::value_to_test_ns::T2> >(
value{{"1", "T2"}, {"2", "T2"}, {"3", nullptr}},
ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::syntax );
}
// success
auto res = try_value_to<
std::map<std::string, ::value_to_test_ns::T2> >(
value{{"1", "T2"}, {"2", "T2"}, {"3", "T2"}});
std::map<std::string, ::value_to_test_ns::T2> >(
value{{"1", "T2"}, {"2", "T2"}, {"3", "T2"}}, ctx... );
BOOST_TEST( res.has_value() );
BOOST_TEST( res->size() == 3 );
}
@@ -538,7 +614,7 @@ public:
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value("not an array"));
value("not an array"), ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::not_array );
@@ -552,7 +628,7 @@ public:
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value{1, 2});
value{1, 2}, ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error().has_location() );
BOOST_TEST( res.error() == error::size_mismatch );
@@ -566,7 +642,7 @@ public:
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value{1, "foobar", false, nullptr, ""});
value{1, "foobar", false, nullptr, ""}, ctx... );
BOOST_TEST( res.has_error() );
BOOST_TEST( res.error() == error::syntax );
}
@@ -578,7 +654,7 @@ public:
bool,
std::nullptr_t,
::value_to_test_ns::T2>>(
value{1, "foobar", false, nullptr, "T2"});
value{1, "foobar", false, nullptr, "T2"}, ctx... );
BOOST_TEST( res.has_value() );
BOOST_TEST( std::get<0>(*res) == 1 );
BOOST_TEST( std::get<1>(*res) == "foobar" );
@@ -586,33 +662,69 @@ public:
}
// rethrowing bad_alloc
BOOST_TEST_THROWS(
try_value_to<value_to_test_ns::T5>(value()),
try_value_to<value_to_test_ns::T5>( value(), ctx... ),
std::bad_alloc);
}
template< class... Context >
static
void
testUserConversion()
testUserConversion( Context const& ... ctx )
{
value_to<value_to_test_ns::T2>(value("T2"));
value_to<value_to_test_ns::T2>( value("T2"), ctx... );
}
void
testContext()
{
value_to<value_to_test_ns::T9>(
value("T9"), value_to_test_ns::custom_context() );
BOOST_TEST_THROWS(
value_to<value_to_test_ns::T9>(
value(), value_to_test_ns::custom_context() ),
system_error);
}
struct run_templated_tests
{
// this overload supports zero or one default constructible contexts
// and used with mp_for_each
template< class... Context >
void operator()( mp11::mp_list< Context... > )
{
testNumberCast( Context()... );
testJsonTypes( Context()... );
testGenerics( Context()... );
testNullptr( Context()... );
testDescribed( Context()... );
testOptional( Context()... );
testVariant( Context()... );
testNonThrowing( Context()... );
testUserConversion( Context()... );
}
};
void
run()
{
testNumberCast();
testJsonTypes();
testGenerics();
mp11::mp_for_each<
mp11::mp_list<
mp11::mp_list<>,
mp11::mp_list<detail::no_context>,
mp11::mp_list<value_to_test_ns::custom_context>,
mp11::mp_list<
std::tuple<value_to_test_ns::custom_context>>,
mp11::mp_list<
std::tuple<
std::tuple<value_to_test_ns::custom_context>>>,
mp11::mp_list<
std::tuple<
detail::no_context, value_to_test_ns::custom_context>>
>>( run_templated_tests() );
testContext();
testContainerHelpers();
testNullptr();
testDescribed();
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
testOptional();
#endif
#ifndef BOOST_NO_CXX17_HDR_VARIANT
testVariant();
#endif // BOOST_NO_CXX17_HDR_VARIANT
testUserConversion();
testNonThrowing();
}
};