mirror of
https://github.com/boostorg/json.git
synced 2026-01-19 04:12:14 +00:00
contextual conversions
This commit is contained in:
123
doc/qbk/conversion/context.qbk
Normal file
123
doc/qbk/conversion/context.qbk
Normal 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]
|
||||
@@ -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]
|
||||
|
||||
@@ -21,6 +21,7 @@ from the standard library.
|
||||
[include custom.qbk]
|
||||
[include nothrow.qbk]
|
||||
[include alloc.qbk]
|
||||
[include context.qbk]
|
||||
[include forward.qbk]
|
||||
|
||||
[endsect]
|
||||
|
||||
@@ -20,5 +20,9 @@
|
||||
[example_validate]
|
||||
[endsect]
|
||||
|
||||
[section Allocator-aware conversion]
|
||||
[example_use_allocator]
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
89
example/use_allocator.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//]
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user