Compare commits

...
Author SHA1 Message Date
Niels Lohmann ebab8a11cc Fix quadratic parent updates for ordered_json with JSON_DIAGNOSTICS
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:13 +02:00
Niels Lohmann f87b707292 Move elements when converting rvalue containers to JSON
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:03 +02:00
Niels Lohmann 420e9218cd Hint the end when converting an object to a standard container
from_json for map-like containers called emplace() without a hint, which
searches the tree for every key. The keys of the source object are sorted, so
inserting with the end as the hint is constant time, as it was in 3.12.0 via
std::inserter. get<std::map<...>>() of a 25,000 entry object: 4.2 ms -> 3.25 ms
(3.12.0: 3.7 ms).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:01 +02:00
Niels Lohmann ad4c240e8b Speed up array-to-container from_json
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 10:52:59 +02:00
9 changed files with 835 additions and 101 deletions

No files matched your search

+11 -1
View File
@@ -68,6 +68,14 @@ basic_json(basic_json&& other) noexcept;
`std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to `string_t`
and a `value_type` from which a `basic_json` value can be constructed.
- **strings**: `string_t`, string literals, and all compatible string containers can be used.
!!! note "Rvalue containers are moved from"
If `val` is an rvalue of a compatible array-like or object-like container other than `array_t`/`object_t`
(e.g., `json j = std::move(vec);` or `json j(std::vector<std::string>{...});`), the elements are moved into
the JSON value instead of copied. Afterwards, the elements of the source container are in a valid but
unspecified state (**moved-from**). Keys of associative containers are `const` and therefore still copied.
Lvalues and `const` rvalues are copied, and the source is left unchanged.
- **numbers**: [`number_integer_t`](number_integer_t.md), [`number_unsigned_t`](number_unsigned_t.md),
[`number_float_t`](number_float_t.md), and all convertible number types such as `int`, `size_t`, `int64_t`, `float`
or `double` can be used.
@@ -236,7 +244,9 @@ basic_json(basic_json&& other) noexcept;
2. No-throw guarantee: this constructor never throws exceptions.
3. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
JSON value.
JSON value. **Exception:** when `val` is an rvalue array-like or object-like container, its elements are moved one
by one; if an exception is thrown in the middle of the conversion (e.g., an allocation failure), the JSON value is
unchanged, but the source container may be left partially moved-from.
4. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
JSON value.
+9
View File
@@ -9,6 +9,15 @@ published on the [GitHub releases page](https://github.com/nlohmann/json/release
This documentation is built from the `develop` branch and may describe changes that are not part of a release
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge.
## Unreleased
- Converting an rvalue container to JSON (`json j = std::move(vec);`, `json j = std::map<std::string, std::string>{...};`)
now **moves** the elements instead of copying them (e.g., `std::vector<std::string>` with 100,000 strings: 4.7 ms to
2.3 ms). Two consequences: the source container is left in a valid but unspecified (moved-from) state, and if an
exception is thrown during the conversion, the source may be partially moved-from, so the strong exception guarantee
no longer holds for the source of an rvalue conversion (the JSON value itself is unaffected). Lvalues and `const`
objects are still copied. See [`basic_json`](../api/basic_json/basic_json.md).
## v3.12.0 (2025-04-11)
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
@@ -266,6 +266,22 @@ template<typename ConstructibleArrayType>
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
// append through push_back() where available; containers without it (such as
// std::set) fall back to insert(end(), ...), which is what std::inserter did
template<typename ConstructibleArrayType, typename ValueType>
auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/)
-> decltype(arr.push_back(std::forward<ValueType>(value)), void())
{
arr.push_back(std::forward<ValueType>(value));
}
template<typename ConstructibleArrayType, typename ValueType>
void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/)
{
using std::end;
arr.insert(end(arr), std::forward<ValueType>(value));
}
template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
@@ -275,17 +291,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
j.template get<typename ConstructibleArrayType::value_type>(),
void())
{
using std::end;
ConstructibleArrayType ret;
from_json_array_reserve(ret, j.size(), priority_tag<1> {});
std::transform(j.begin(), j.end(),
std::inserter(ret, end(ret)), [](const BasicJsonType & i)
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
for (const auto& elem : *inner_array)
{
// get<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType
return i.template get<typename ConstructibleArrayType::value_type>();
});
from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {});
}
arr = std::move(ret);
}
@@ -383,9 +397,13 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
using value_type = typename ConstructibleObjectType::value_type;
for (const auto& p : *inner_object)
{
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
// the keys of the source are sorted for the usual object types, so
// hinting the end makes every insertion constant-time (for containers
// that order their keys the same way)
ret.insert(ret.end(), value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()));
}
obj = std::move(ret);
}
@@ -167,6 +167,17 @@ struct external_constructor<value_t::array>
j.replace_value(value_t::array, value);
}
/// an rvalue container: move its elements
template<typename BasicJsonType, typename CompatibleArrayType>
static void construct_moving(BasicJsonType& j, CompatibleArrayType& arr)
{
using std::begin;
using std::end;
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
j.replace_value(value_t::array, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{
@@ -217,6 +228,17 @@ struct external_constructor<value_t::object>
j.replace_value(value_t::object, value);
}
/// an rvalue container: move its values
template<typename BasicJsonType, typename CompatibleObjectType>
static void construct_moving(BasicJsonType& j, CompatibleObjectType& obj)
{
using std::begin;
using std::end;
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
j.replace_value(value_t::object, value);
}
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
@@ -354,6 +376,66 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, arr);
}
// types that have a dedicated to_json overload and must not be treated as generic containers
template<typename T>
struct has_dedicated_to_json : std::false_type {};
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
template<>
struct has_dedicated_to_json<std_fs::path> : std::true_type {};
#endif
#if defined(__cpp_lib_char8_t)
template<typename Tr, typename Allocator>
struct has_dedicated_to_json<std::basic_string<char8_t, Tr, Allocator>> : std::true_type {};
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
template<typename T>
struct is_range_view_type : is_compatible_range_view<T> {};
#else
template<typename T>
struct is_range_view_type : std::false_type {};
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename T>
struct is_enum_keyed_map_type : is_enum_keyed_map<T> {};
#else
template<typename T>
struct is_enum_keyed_map_type : std::false_type {};
#endif
// rvalue (non-const) container: the generic to_json takes its elements by move
template<typename T>
using is_movable_container_rvalue = conjunction <
negation<std::is_lvalue_reference<T>>,
negation<std::is_const<T>>,
negation<has_dedicated_to_json<T>>,
negation<is_basic_json<T>>,
negation<is_range_view_type<T>>,
negation<is_enum_keyed_map_type<T>>,
negation<std::is_same<T, std::vector<bool>>> >;
// rvalue array-like containers
template<typename BasicJsonType, typename T>
using is_rvalue_array_to_json = conjunction <
is_movable_container_rvalue<T>,
negation<std::is_same<T, typename BasicJsonType::array_t>>,
negation<std::is_same<T, typename BasicJsonType::binary_t>>,
is_compatible_array_type<BasicJsonType, T>,
negation<is_compatible_object_type<BasicJsonType, T>>,
negation<is_compatible_string_type<BasicJsonType, T>>,
negation<is_compatible_binary_type<BasicJsonType, T>> >;
// rvalue containers: their elements are moved instead of copied
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int > = 0 >
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
{
external_constructor<value_t::array>::construct_moving(j, arr);
}
#if JSON_HAS_RANGE_VIEW_CONVERSION
template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
@@ -427,6 +509,20 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
}
#endif
// rvalue object-like containers
template<typename BasicJsonType, typename T>
using is_rvalue_object_to_json = conjunction <
is_movable_container_rvalue<T>,
negation<std::is_same<T, typename BasicJsonType::object_t>>,
is_compatible_object_type<BasicJsonType, T> >;
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int > = 0 >
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
{
external_constructor<value_t::object>::construct_moving(j, obj);
}
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
+57 -42
View File
@@ -1079,51 +1079,64 @@ public:
return next;
}
/// @brief the capacity of the stored array, or unknown_size()
/// @brief the capacity of the stored array or object, or unknown_size()
/// Only JSON_DIAGNOSTICS uses the value, to detect a reallocation that
/// would invalidate the parent pointers. Array types that do not have a
/// capacity() member function report unknown_size(), which is treated as
/// "the elements may have moved".
/// would invalidate the parent pointers. Containers that do not have a
/// capacity() member function report unknown_size() if their elements may
/// move (arrays, ordered_map) and 0 if they never do (std::map).
#if JSON_DIAGNOSTICS
template < typename A = array_t, detail::enable_if_t < detail::has_capacity<A>::value, int > = 0 >
std::size_t array_capacity() const noexcept
template < typename C, detail::enable_if_t < detail::has_capacity<C>::value, int > = 0 >
static std::size_t capacity_of(const C& container) noexcept
{
return m_data.m_value.array->capacity();
return container.capacity();
}
template < typename A = array_t, detail::enable_if_t < !detail::has_capacity<A>::value, int > = 0 >
std::size_t array_capacity() const noexcept
template < typename C, detail::enable_if_t < !detail::has_capacity<C>::value, int > = 0 >
static std::size_t capacity_of(const C& /*container*/) noexcept
{
return detail::unknown_size();
return std::is_same<C, array_t>::value || detail::is_ordered_map<C>::value ? detail::unknown_size() : 0;
}
std::size_t container_capacity() const noexcept
{
return is_array() ? capacity_of(*m_data.m_value.array) : capacity_of(*m_data.m_value.object);
}
#else
static constexpr std::size_t array_capacity() noexcept
static constexpr std::size_t container_capacity() noexcept
{
return detail::unknown_size();
}
#endif
/// @brief set the parent of a value that has just been added to an array
/// @param j the added value
/// @param old_capacity the value @ref array_capacity() returned before the
/// @brief set the parent of a value that has just been added to an array or object
/// @param j the added (or looked-up) value
/// @param old_capacity the value @ref container_capacity() returned before the
/// insertion
reference set_parent_after_array_insert(reference j, std::size_t old_capacity)
reference set_parent_after_insert(reference j, std::size_t old_capacity)
{
#if JSON_DIAGNOSTICS
// see https://github.com/nlohmann/json/issues/2838
JSON_ASSERT(type() == value_t::array);
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|| array_capacity() != old_capacity))
|| container_capacity() != old_capacity))
{
// the capacity has changed, or the array type does not let us tell:
// the elements may have moved, so update all parents
// the capacity has changed, or the container type does not let us
// tell: the elements may have moved, so update all parents
set_parents();
return j;
}
#else
static_cast<void>(old_capacity);
#endif
return set_parent(j);
return set_parent_at(j);
}
/// @brief set the parent of an element accessed via at(); never moves elements
reference set_parent_at(reference j) noexcept
{
#if JSON_DIAGNOSTICS
j.m_parent = this;
#endif
return j;
}
reference set_parent(reference j)
@@ -2557,7 +2570,6 @@ public:
copy_leaf_value(other, *this);
}
set_parents();
assert_invariant();
}
@@ -3445,7 +3457,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/at/
reference at(size_type idx)
{
return set_parent(array_at(*this, idx));
return set_parent_at(array_at(*this, idx));
}
/// @brief access specified array element with bounds checking
@@ -3459,7 +3471,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/at/
reference at(const typename object_t::key_type& key)
{
return set_parent(object_at(*this, key));
return set_parent_at(object_at(*this, key));
}
/// @brief access specified object element with bounds checking
@@ -3468,7 +3480,7 @@ public:
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
reference at(KeyType && key)
{
return set_parent(object_at(*this, std::forward<KeyType>(key)));
return set_parent_at(object_at(*this, std::forward<KeyType>(key)));
}
/// @brief access specified object element with bounds checking
@@ -3513,13 +3525,13 @@ public:
#if JSON_DIAGNOSTICS
// remember array size & capacity before resizing
const auto old_size = m_data.m_value.array->size();
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
#endif
m_data.m_value.array->resize(idx + 1);
#if JSON_DIAGNOSTICS
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|| array_capacity() != old_capacity))
|| container_capacity() != old_capacity))
{
// capacity has changed: update all parents
set_parents();
@@ -3566,8 +3578,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = container_capacity();
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
return set_parent(result.first->second);
return set_parent_after_insert(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -3617,8 +3630,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = container_capacity();
auto result = m_data.m_value.object->emplace(lookup_key(std::forward<KeyType>(key)), nullptr);
return set_parent(result.first->second);
return set_parent_after_insert(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -4524,9 +4538,9 @@ public:
}
// add the element to the array (move semantics)
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(std::move(val));
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
// if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
}
@@ -4555,9 +4569,9 @@ public:
}
// add the element to the array
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(val);
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
}
/// @brief add an object to an array
@@ -4585,8 +4599,9 @@ public:
}
// add the element to the object
const auto old_capacity = container_capacity();
auto res = m_data.m_value.object->insert(val);
set_parent(res.first->second);
set_parent_after_insert(res.first->second, old_capacity);
}
/// @brief add an object to an object
@@ -4639,9 +4654,9 @@ public:
}
// add the element to the array (perfect forwarding)
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
m_data.m_value.array->emplace_back(std::forward<Args>(args)...);
return set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
return set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
}
/// @brief add an object to an object if key does not exist
@@ -4662,8 +4677,9 @@ public:
}
// add the element to the array (perfect forwarding)
const auto old_capacity = container_capacity();
auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);
set_parent(res.first->second);
set_parent_after_insert(res.first->second, old_capacity);
// create a result iterator and set iterator to the result of emplace
auto it = begin();
@@ -4962,9 +4978,9 @@ public:
continue;
}
}
// set_parent() also repairs the other members, which ordered_json
// relocates when adding a key makes its vector grow
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
// operator[] repairs the other members if ordered_json relocates
// them; assigning through it keeps this O(1) per key
operator[](it.key()) = std::move(it.value());
}
}
@@ -5014,9 +5030,8 @@ public:
continue;
}
}
// set_parent() also repairs the other members, which ordered_json
// relocates when adding a key makes its vector grow
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
// operator[] repairs the other members if ordered_json relocates them
target->operator[](first.key()) = std::move(first.value());
++first;
}
}
+179 -50
View File
@@ -5970,6 +5970,22 @@ template<typename ConstructibleArrayType>
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
// append through push_back() where available; containers without it (such as
// std::set) fall back to insert(end(), ...), which is what std::inserter did
template<typename ConstructibleArrayType, typename ValueType>
auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/)
-> decltype(arr.push_back(std::forward<ValueType>(value)), void())
{
arr.push_back(std::forward<ValueType>(value));
}
template<typename ConstructibleArrayType, typename ValueType>
void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/)
{
using std::end;
arr.insert(end(arr), std::forward<ValueType>(value));
}
template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
@@ -5979,17 +5995,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
j.template get<typename ConstructibleArrayType::value_type>(),
void())
{
using std::end;
ConstructibleArrayType ret;
from_json_array_reserve(ret, j.size(), priority_tag<1> {});
std::transform(j.begin(), j.end(),
std::inserter(ret, end(ret)), [](const BasicJsonType & i)
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
for (const auto& elem : *inner_array)
{
// get<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType
return i.template get<typename ConstructibleArrayType::value_type>();
});
from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {});
}
arr = std::move(ret);
}
@@ -6087,9 +6101,13 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
using value_type = typename ConstructibleObjectType::value_type;
for (const auto& p : *inner_object)
{
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
// the keys of the source are sorted for the usual object types, so
// hinting the end makes every insertion constant-time (for containers
// that order their keys the same way)
ret.insert(ret.end(), value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()));
}
obj = std::move(ret);
}
@@ -7122,6 +7140,17 @@ struct external_constructor<value_t::array>
j.replace_value(value_t::array, value);
}
/// an rvalue container: move its elements
template<typename BasicJsonType, typename CompatibleArrayType>
static void construct_moving(BasicJsonType& j, CompatibleArrayType& arr)
{
using std::begin;
using std::end;
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
j.replace_value(value_t::array, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{
@@ -7172,6 +7201,17 @@ struct external_constructor<value_t::object>
j.replace_value(value_t::object, value);
}
/// an rvalue container: move its values
template<typename BasicJsonType, typename CompatibleObjectType>
static void construct_moving(BasicJsonType& j, CompatibleObjectType& obj)
{
using std::begin;
using std::end;
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
j.replace_value(value_t::object, value);
}
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
@@ -7309,6 +7349,66 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, arr);
}
// types that have a dedicated to_json overload and must not be treated as generic containers
template<typename T>
struct has_dedicated_to_json : std::false_type {};
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
template<>
struct has_dedicated_to_json<std_fs::path> : std::true_type {};
#endif
#if defined(__cpp_lib_char8_t)
template<typename Tr, typename Allocator>
struct has_dedicated_to_json<std::basic_string<char8_t, Tr, Allocator>> : std::true_type {};
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
template<typename T>
struct is_range_view_type : is_compatible_range_view<T> {};
#else
template<typename T>
struct is_range_view_type : std::false_type {};
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename T>
struct is_enum_keyed_map_type : is_enum_keyed_map<T> {};
#else
template<typename T>
struct is_enum_keyed_map_type : std::false_type {};
#endif
// rvalue (non-const) container: the generic to_json takes its elements by move
template<typename T>
using is_movable_container_rvalue = conjunction <
negation<std::is_lvalue_reference<T>>,
negation<std::is_const<T>>,
negation<has_dedicated_to_json<T>>,
negation<is_basic_json<T>>,
negation<is_range_view_type<T>>,
negation<is_enum_keyed_map_type<T>>,
negation<std::is_same<T, std::vector<bool>>> >;
// rvalue array-like containers
template<typename BasicJsonType, typename T>
using is_rvalue_array_to_json = conjunction <
is_movable_container_rvalue<T>,
negation<std::is_same<T, typename BasicJsonType::array_t>>,
negation<std::is_same<T, typename BasicJsonType::binary_t>>,
is_compatible_array_type<BasicJsonType, T>,
negation<is_compatible_object_type<BasicJsonType, T>>,
negation<is_compatible_string_type<BasicJsonType, T>>,
negation<is_compatible_binary_type<BasicJsonType, T>> >;
// rvalue containers: their elements are moved instead of copied
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int > = 0 >
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
{
external_constructor<value_t::array>::construct_moving(j, arr);
}
#if JSON_HAS_RANGE_VIEW_CONVERSION
template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
@@ -7382,6 +7482,20 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
}
#endif
// rvalue object-like containers
template<typename BasicJsonType, typename T>
using is_rvalue_object_to_json = conjunction <
is_movable_container_rvalue<T>,
negation<std::is_same<T, typename BasicJsonType::object_t>>,
is_compatible_object_type<BasicJsonType, T> >;
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int > = 0 >
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
{
external_constructor<value_t::object>::construct_moving(j, obj);
}
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
@@ -30705,51 +30819,64 @@ public:
return next;
}
/// @brief the capacity of the stored array, or unknown_size()
/// @brief the capacity of the stored array or object, or unknown_size()
/// Only JSON_DIAGNOSTICS uses the value, to detect a reallocation that
/// would invalidate the parent pointers. Array types that do not have a
/// capacity() member function report unknown_size(), which is treated as
/// "the elements may have moved".
/// would invalidate the parent pointers. Containers that do not have a
/// capacity() member function report unknown_size() if their elements may
/// move (arrays, ordered_map) and 0 if they never do (std::map).
#if JSON_DIAGNOSTICS
template < typename A = array_t, detail::enable_if_t < detail::has_capacity<A>::value, int > = 0 >
std::size_t array_capacity() const noexcept
template < typename C, detail::enable_if_t < detail::has_capacity<C>::value, int > = 0 >
static std::size_t capacity_of(const C& container) noexcept
{
return m_data.m_value.array->capacity();
return container.capacity();
}
template < typename A = array_t, detail::enable_if_t < !detail::has_capacity<A>::value, int > = 0 >
std::size_t array_capacity() const noexcept
template < typename C, detail::enable_if_t < !detail::has_capacity<C>::value, int > = 0 >
static std::size_t capacity_of(const C& /*container*/) noexcept
{
return detail::unknown_size();
return std::is_same<C, array_t>::value || detail::is_ordered_map<C>::value ? detail::unknown_size() : 0;
}
std::size_t container_capacity() const noexcept
{
return is_array() ? capacity_of(*m_data.m_value.array) : capacity_of(*m_data.m_value.object);
}
#else
static constexpr std::size_t array_capacity() noexcept
static constexpr std::size_t container_capacity() noexcept
{
return detail::unknown_size();
}
#endif
/// @brief set the parent of a value that has just been added to an array
/// @param j the added value
/// @param old_capacity the value @ref array_capacity() returned before the
/// @brief set the parent of a value that has just been added to an array or object
/// @param j the added (or looked-up) value
/// @param old_capacity the value @ref container_capacity() returned before the
/// insertion
reference set_parent_after_array_insert(reference j, std::size_t old_capacity)
reference set_parent_after_insert(reference j, std::size_t old_capacity)
{
#if JSON_DIAGNOSTICS
// see https://github.com/nlohmann/json/issues/2838
JSON_ASSERT(type() == value_t::array);
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|| array_capacity() != old_capacity))
|| container_capacity() != old_capacity))
{
// the capacity has changed, or the array type does not let us tell:
// the elements may have moved, so update all parents
// the capacity has changed, or the container type does not let us
// tell: the elements may have moved, so update all parents
set_parents();
return j;
}
#else
static_cast<void>(old_capacity);
#endif
return set_parent(j);
return set_parent_at(j);
}
/// @brief set the parent of an element accessed via at(); never moves elements
reference set_parent_at(reference j) noexcept
{
#if JSON_DIAGNOSTICS
j.m_parent = this;
#endif
return j;
}
reference set_parent(reference j)
@@ -32183,7 +32310,6 @@ public:
copy_leaf_value(other, *this);
}
set_parents();
assert_invariant();
}
@@ -33071,7 +33197,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/at/
reference at(size_type idx)
{
return set_parent(array_at(*this, idx));
return set_parent_at(array_at(*this, idx));
}
/// @brief access specified array element with bounds checking
@@ -33085,7 +33211,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/at/
reference at(const typename object_t::key_type& key)
{
return set_parent(object_at(*this, key));
return set_parent_at(object_at(*this, key));
}
/// @brief access specified object element with bounds checking
@@ -33094,7 +33220,7 @@ public:
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
reference at(KeyType && key)
{
return set_parent(object_at(*this, std::forward<KeyType>(key)));
return set_parent_at(object_at(*this, std::forward<KeyType>(key)));
}
/// @brief access specified object element with bounds checking
@@ -33139,13 +33265,13 @@ public:
#if JSON_DIAGNOSTICS
// remember array size & capacity before resizing
const auto old_size = m_data.m_value.array->size();
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
#endif
m_data.m_value.array->resize(idx + 1);
#if JSON_DIAGNOSTICS
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|| array_capacity() != old_capacity))
|| container_capacity() != old_capacity))
{
// capacity has changed: update all parents
set_parents();
@@ -33192,8 +33318,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = container_capacity();
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
return set_parent(result.first->second);
return set_parent_after_insert(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -33243,8 +33370,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = container_capacity();
auto result = m_data.m_value.object->emplace(lookup_key(std::forward<KeyType>(key)), nullptr);
return set_parent(result.first->second);
return set_parent_after_insert(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -34150,9 +34278,9 @@ public:
}
// add the element to the array (move semantics)
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(std::move(val));
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
// if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
}
@@ -34181,9 +34309,9 @@ public:
}
// add the element to the array
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(val);
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
}
/// @brief add an object to an array
@@ -34211,8 +34339,9 @@ public:
}
// add the element to the object
const auto old_capacity = container_capacity();
auto res = m_data.m_value.object->insert(val);
set_parent(res.first->second);
set_parent_after_insert(res.first->second, old_capacity);
}
/// @brief add an object to an object
@@ -34265,9 +34394,9 @@ public:
}
// add the element to the array (perfect forwarding)
const auto old_capacity = array_capacity();
const auto old_capacity = container_capacity();
m_data.m_value.array->emplace_back(std::forward<Args>(args)...);
return set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
return set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
}
/// @brief add an object to an object if key does not exist
@@ -34288,8 +34417,9 @@ public:
}
// add the element to the array (perfect forwarding)
const auto old_capacity = container_capacity();
auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);
set_parent(res.first->second);
set_parent_after_insert(res.first->second, old_capacity);
// create a result iterator and set iterator to the result of emplace
auto it = begin();
@@ -34588,9 +34718,9 @@ public:
continue;
}
}
// set_parent() also repairs the other members, which ordered_json
// relocates when adding a key makes its vector grow
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
// operator[] repairs the other members if ordered_json relocates
// them; assigning through it keeps this O(1) per key
operator[](it.key()) = std::move(it.value());
}
}
@@ -34640,9 +34770,8 @@ public:
continue;
}
}
// set_parent() also repairs the other members, which ordered_json
// relocates when adding a key makes its vector grow
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
// operator[] repairs the other members if ordered_json relocates them
target->operator[](first.key()) = std::move(first.value());
++first;
}
}
+26
View File
@@ -9,7 +9,10 @@
#include <benchmark/benchmark.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include <array>
#include <deque>
#include <numeric>
#include <set>
#include <vector>
#include <test_data.hpp>
@@ -621,4 +624,27 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
// conversion of a JSON array into standard containers
template<typename Container>
static void GetArray(benchmark::State& state)
{
json j = json::array();
for (int i = 0; i < 1000; ++i)
{
j.push_back(i);
}
for (auto _ : state)
{
auto c = j.get<Container>();
benchmark::DoNotOptimize(c);
}
state.SetItemsProcessed(state.iterations() * 1000);
}
BENCHMARK_TEMPLATE(GetArray, std::vector<int>);
BENCHMARK_TEMPLATE(GetArray, std::deque<int>);
BENCHMARK_TEMPLATE(GetArray, std::set<int>);
BENCHMARK_TEMPLATE(GetArray, std::array<int, 1000>);
BENCHMARK_MAIN();
+174
View File
@@ -13,9 +13,11 @@
#endif
#define JSON_DIAGNOSTICS 1
#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
using nlohmann::json;
using nlohmann::ordered_json;
#include <map>
#include <unordered_map>
@@ -631,3 +633,175 @@ TEST_CASE("Better diagnostics past the descent bound of update(), merge_patch()
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
}
}
namespace
{
// check that every child's parent pointer points to the containing value
template<typename BasicJsonType>
bool parents_consistent(const BasicJsonType& j)
{
bool ok = true;
if (j.is_array() || j.is_object())
{
for (const auto& child : j)
{
ok = ok && child.m_parent == &j && parents_consistent(child);
}
}
return ok;
}
} // namespace
TEST_CASE_TEMPLATE("Parent pointers stay consistent", J, json, ordered_json)
{
SECTION("parse, copy, move, assign, swap")
{
J j = J::parse(R"({"a":[1,{"b":[2,3]},[4]],"c":{"d":{"e":[]}}})");
CHECK(parents_consistent(j));
J copy(j);
CHECK(parents_consistent(copy));
J moved(std::move(copy));
CHECK(parents_consistent(moved));
J assigned;
assigned = j;
CHECK(parents_consistent(assigned));
J other = {1, 2, {{"k", J::array({1, 2})}}};
other.swap(assigned);
CHECK(parents_consistent(other));
CHECK(parents_consistent(assigned));
}
SECTION("push_back growth")
{
J arr = J::array();
for (int i = 0; i < 500; ++i)
{
arr.push_back(J::array({i}));
}
CHECK(parents_consistent(arr));
J obj = J::object();
for (int i = 0; i < 500; ++i)
{
obj.push_back({std::to_string(i), J::array({i})});
}
CHECK(parents_consistent(obj));
}
SECTION("emplace_back and emplace growth")
{
J arr = J::array();
J obj = J::object();
for (int i = 0; i < 500; ++i)
{
arr.emplace_back(J::array({i}));
obj.emplace(std::to_string(i), J::array({i}));
}
CHECK(parents_consistent(arr));
CHECK(parents_consistent(obj));
}
SECTION("operator[] growth and at()")
{
J obj = J::object();
for (int i = 0; i < 500; ++i)
{
obj[std::to_string(i)] = J::array({i});
CHECK(obj[std::to_string(i / 2)].m_parent == &obj);
}
CHECK(parents_consistent(obj));
J arr = J::array();
for (int i = 0; i < 500; ++i)
{
arr[static_cast<size_t>(i)] = J::object({{"x", i}});
}
for (size_t i = 0; i < 500; ++i)
{
CHECK(arr.at(i).m_parent == &arr);
}
CHECK(obj.at("3").m_parent == &obj);
CHECK(parents_consistent(arr));
CHECK(parents_consistent(obj));
}
}
TEST_CASE_TEMPLATE("update() and merge_patch() keep parent pointers and diagnostic paths", J, json, ordered_json)
{
// enough keys to make the vector behind ordered_json reallocate several times
const auto make_object = [](int first, int last)
{
J o = J::object();
for (int i = first; i < last; ++i)
{
o[std::to_string(i)] = J::object({{"x", J::array({i})}});
}
return o;
};
// const access, so operator[] cannot repair the parent pointers
const auto check_path = [](const J & j, const std::string & key)
{
const std::string expected = "[json.exception.type_error.304] (/" + key + "/x) cannot use at() with array";
CHECK_THROWS_WITH_AS(j[key]["x"].at("y"), expected.c_str(), json::type_error);
};
SECTION("update(const_reference)")
{
J j = make_object(0, 200);
j.update(make_object(100, 400));
CHECK(j.size() == 400);
CHECK(parents_consistent(j));
check_path(j, "0");
check_path(j, "150");
check_path(j, "399");
}
SECTION("update(const_reference, merge_objects)")
{
J j = make_object(0, 200);
j["50"]["y"] = 1;
J other = make_object(100, 400);
other["50"] = J::object({{"z", J::array({1})}});
j.update(other, true);
CHECK(j.size() == 400);
CHECK(j["50"].size() == 3);
CHECK(parents_consistent(j));
check_path(j, "0");
check_path(j, "399");
}
SECTION("update(first, last)")
{
J j = make_object(0, 200);
J other = make_object(100, 400);
j.update(other.begin(), other.end());
CHECK(j.size() == 400);
CHECK(parents_consistent(j));
check_path(j, "0");
check_path(j, "399");
J k = make_object(0, 200);
k.update(other.begin(), other.end(), true);
CHECK(k.size() == 400);
CHECK(parents_consistent(k));
check_path(k, "399");
}
SECTION("merge_patch()")
{
J j = make_object(0, 200);
J patch = make_object(100, 400);
patch["0"] = nullptr;
j.merge_patch(patch);
CHECK(j.size() == 399);
CHECK_FALSE(j.contains("0"));
CHECK(parents_consistent(j));
check_path(j, "1");
check_path(j, "399");
}
}
+257
View File
@@ -0,0 +1,257 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <array>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <valarray>
#include <vector>
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1)
#if __has_include(<filesystem>)
#include <filesystem>
#define TO_JSON_RVALUE_HAS_FS 1
#endif
#endif
namespace
{
// element type that counts how it is converted to JSON: a conversion from an
// lvalue copies, a conversion from an rvalue moves
struct tracked
{
std::string payload;
bool moved_from = false;
explicit tracked(std::string p) : payload(std::move(p)) {}
tracked(const tracked&) = default;
tracked& operator=(const tracked&) = default;
tracked(tracked&& other) noexcept : payload(std::move(other.payload)), moved_from(other.moved_from)
{
other.moved_from = true;
}
tracked& operator=(tracked&& other) noexcept
{
payload = std::move(other.payload);
moved_from = other.moved_from;
other.moved_from = true;
return *this;
}
~tracked() = default;
};
int tracked_copies = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,misc-use-internal-linkage)
int tracked_moves = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,misc-use-internal-linkage)
void to_json(json& j, const tracked& t)
{
++tracked_copies;
j = t.payload;
}
void to_json(json& j, tracked&& t)
{
++tracked_moves;
j = std::move(t.payload);
t.moved_from = true;
}
// user type with its own rvalue-aware serializer
struct udt_container
{
std::vector<int> values;
};
} // namespace
namespace nlohmann
{
template<>
struct adl_serializer<udt_container>
{
static void to_json(json& j, const udt_container& c)
{
j = c.values;
j.push_back("copied");
}
static void to_json(json& j, udt_container&& c)
{
j = std::move(c.values);
j.push_back("moved");
}
};
} // namespace nlohmann
TEST_CASE("to_json from rvalue containers")
{
const std::string long_a(64, 'a');
const std::string long_b(64, 'b');
SECTION("vector<std::string>: elements are moved")
{
std::vector<std::string> v{long_a, long_b};
const json j = std::move(v);
CHECK(j == json::array({long_a, long_b}));
for (const auto& s : v) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
{
CHECK(s.empty()); // long strings leave the source element empty when moved
}
}
SECTION("vector<std::string>: lvalues are copied and left untouched")
{
const std::vector<std::string> v{long_a, long_b};
const json j = v;
CHECK(j == json::array({long_a, long_b}));
CHECK(v[0] == long_a);
CHECK(v[1] == long_b);
std::vector<std::string> v2{long_a, long_b};
const json j2 = v2;
CHECK(j2 == j);
CHECK(v2[0] == long_a);
CHECK(v2[1] == long_b);
}
SECTION("tracked elements: rvalue moves, lvalue copies")
{
tracked_copies = 0;
tracked_moves = 0;
std::vector<tracked> v;
v.emplace_back("x");
v.emplace_back("y");
const json copy = v;
CHECK(copy == json::array({"x", "y"}));
CHECK(tracked_copies == 2);
CHECK(tracked_moves == 0);
CHECK_FALSE(v[0].moved_from);
const json moved = std::move(v);
CHECK(moved == copy);
CHECK(tracked_copies == 2);
CHECK(tracked_moves == 2);
}
SECTION("const rvalue is copied")
{
tracked_copies = 0;
tracked_moves = 0;
const std::vector<tracked> v(2, tracked("z"));
const json j = std::move(v); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
CHECK(j == json::array({"z", "z"}));
CHECK(tracked_copies == 2);
CHECK(tracked_moves == 0);
}
SECTION("other sequence containers")
{
CHECK(json(std::deque<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
CHECK(json(std::list<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
CHECK(json(std::forward_list<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
CHECK(json(std::array<std::string, 2> {{long_a, long_b}}) == json::array({long_a, long_b}));
CHECK(json(std::set<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
CHECK(json(std::vector<bool> {true, false}) == json::array({true, false}));
CHECK(json(std::vector<int> {1, 2, 3}) == json::array({1, 2, 3}));
CHECK(json(std::vector<std::vector<std::string>> {{long_a}, {long_b}}) == json::array({json::array({long_a}), json::array({long_b})}));
CHECK(json(std::vector<json> {1, "a"}) == json::array({1, "a"}));
}
SECTION("valarray")
{
std::valarray<int> va(3);
va[0] = 1;
va[1] = 2;
va[2] = 3;
CHECK(json(std::move(va)) == json::array({1, 2, 3})); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
CHECK(json(std::valarray<int>(2)) == json::array({0, 0}));
}
SECTION("array_t and json arrays")
{
json::array_t arr{1, 2};
CHECK(json(std::move(arr)) == json::array({1, 2}));
json a = json::array({1, 2});
const json b = std::move(a);
CHECK(b == json::array({1, 2}));
}
SECTION("object-like containers")
{
std::map<std::string, std::string> m{{"a", long_a}, {"b", long_b}};
const json j = std::move(m);
CHECK(j == json({{"a", long_a}, {"b", long_b}}));
for (const auto& kv : m) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
{
CHECK(kv.second.empty()); // values moved, keys (const) copied
}
const std::map<std::string, std::string> cm{{"a", long_a}};
const json jc = cm;
CHECK(jc["a"] == long_a);
CHECK(cm.at("a") == long_a);
std::unordered_map<std::string, std::string> um{{"a", long_a}};
CHECK(json(std::move(um)) == json({{"a", long_a}}));
std::multimap<std::string, int> mm{{"a", 1}};
CHECK(json(std::move(mm)) == json({{"a", 1}}));
tracked_copies = 0;
tracked_moves = 0;
std::map<std::string, tracked> mt;
mt.emplace("k", tracked("v"));
const json jt = std::move(mt);
CHECK(jt == json({{"k", "v"}}));
CHECK(tracked_moves == 1);
CHECK(tracked_copies == 0);
json::object_t obj{{"a", 1}};
CHECK(json(std::move(obj)) == json({{"a", 1}}));
}
SECTION("user type with its own to_json overloads")
{
udt_container c{{1, 2}};
const json lv = c;
CHECK(lv == json::array({1, 2, "copied"}));
const json rv = std::move(c);
CHECK(rv == json::array({1, 2, "moved"}));
}
SECTION("assignment and implicit conversion")
{
std::vector<std::string> v{long_a};
json j;
j = std::move(v);
CHECK(j == json::array({long_a}));
json k = {std::vector<std::string>{long_b}};
CHECK(k.size() == 1);
}
#ifdef TO_JSON_RVALUE_HAS_FS
SECTION("std::filesystem::path rvalue is still a string")
{
CHECK(json(std::filesystem::path("a/b")) == json("a/b"));
#ifdef __cpp_lib_char8_t
CHECK(json(std::u8string(u8"a/b")) == json("a/b"));
#endif
}
#endif
}