Compare commits

..
Author SHA1 Message Date
Niels Lohmann 9181e24f52 Fix quadratic parent updates for ordered_json with JSON_DIAGNOSTICS
set_parent() re-parented the whole container for ordered_json on every
call (push_back, emplace_back, at(), operator[] on existing keys),
making array building quadratic. at() and array append now set only the
one child's parent; object insertion re-parents only if the vector
capacity changed. Also drop the redundant set_parents() in the copy
constructor. Adds a parent-pointer invariant test.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 22:18:39 +02:00
7 changed files with 229 additions and 447 deletions

No files matched your search

+1 -11
View File
@@ -68,14 +68,6 @@ 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.
@@ -244,9 +236,7 @@ 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. **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.
JSON value.
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,15 +9,6 @@ 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.
@@ -167,17 +167,6 @@ 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)
{
@@ -228,17 +217,6 @@ 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)
@@ -376,45 +354,6 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, arr);
}
#if defined(__cpp_lib_char8_t)
// std::basic_string<char8_t> is converted to a string by a dedicated to_json overload
template<typename T>
struct is_u8string : std::false_type {};
template<typename Tr, typename Allocator>
struct is_u8string<std::basic_string<char8_t, Tr, Allocator>> : std::true_type {};
#endif
// rvalue containers: their elements are moved instead of copied
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_lvalue_reference<CompatibleArrayType>::value
&& !std::is_const<CompatibleArrayType>::value
&& !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value
&& !std::is_same<CompatibleArrayType, std::vector<bool>>::value
&& !is_basic_json<CompatibleArrayType>::value
&& is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value
&& !is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value
&& !is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value
&& !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value
&& !is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
&& !std::is_same<CompatibleArrayType, std_fs::path>::value // has its own to_json
#endif
#if defined(__cpp_lib_char8_t)
&& !is_u8string<CompatibleArrayType>::value // has its own to_json
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleArrayType>::value
#endif
, 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
@@ -488,21 +427,6 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
}
#endif
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_lvalue_reference<CompatibleObjectType>::value
&& !std::is_const<CompatibleObjectType>::value
&& !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value
&& is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value
&& !is_basic_json<CompatibleObjectType>::value
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleObjectType>::value
#endif
, 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)
{
+65 -9
View File
@@ -1123,7 +1123,60 @@ public:
#else
static_cast<void>(old_capacity);
#endif
return set_parent(j);
set_parent_plain(j);
return j;
}
/// @brief capacity of the stored object if it is vector-backed (ordered_map), else 0
#if JSON_DIAGNOSTICS
template < typename O = object_t, detail::enable_if_t < detail::has_capacity<O>::value, int > = 0 >
std::size_t object_capacity() const noexcept
{
return m_data.m_value.object->capacity();
}
template < typename O = object_t, detail::enable_if_t < !detail::has_capacity<O>::value, int > = 0 >
std::size_t object_capacity() const noexcept
{
return 0;
}
#else
static constexpr std::size_t object_capacity() noexcept
{
return 0;
}
#endif
/// @brief set parent of an element that was just looked up or inserted into an object
reference set_parent_after_object_emplace(reference j, std::size_t old_capacity)
{
#if JSON_DIAGNOSTICS
if (JSON_HEDLEY_UNLIKELY(object_capacity() != old_capacity))
{
set_parents();
return j;
}
j.m_parent = this;
#else
static_cast<void>(old_capacity);
#endif
return j;
}
void set_parent_plain(reference j) noexcept
{
#if JSON_DIAGNOSTICS
j.m_parent = this;
#else
static_cast<void>(j);
#endif
}
/// @brief set the parent of an element accessed via at(); never moves elements
reference set_parent_at(reference j) noexcept
{
set_parent_plain(j);
return j;
}
reference set_parent(reference j)
@@ -2557,7 +2610,6 @@ public:
copy_leaf_value(other, *this);
}
set_parents();
assert_invariant();
}
@@ -3445,7 +3497,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 +3511,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 +3520,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
@@ -3566,8 +3618,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = object_capacity();
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
return set_parent(result.first->second);
return set_parent_after_object_emplace(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -3617,8 +3670,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = object_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_object_emplace(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -4585,8 +4639,9 @@ public:
}
// add the element to the object
const auto old_capacity = object_capacity();
auto res = m_data.m_value.object->insert(val);
set_parent(res.first->second);
set_parent_after_object_emplace(res.first->second, old_capacity);
}
/// @brief add an object to an object
@@ -4662,8 +4717,9 @@ public:
}
// add the element to the array (perfect forwarding)
const auto old_capacity = object_capacity();
auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);
set_parent(res.first->second);
set_parent_after_object_emplace(res.first->second, old_capacity);
// create a result iterator and set iterator to the result of emplace
auto it = begin();
+65 -85
View File
@@ -7114,17 +7114,6 @@ 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)
{
@@ -7175,17 +7164,6 @@ 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)
@@ -7323,45 +7301,6 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, arr);
}
#if defined(__cpp_lib_char8_t)
// std::basic_string<char8_t> is converted to a string by a dedicated to_json overload
template<typename T>
struct is_u8string : std::false_type {};
template<typename Tr, typename Allocator>
struct is_u8string<std::basic_string<char8_t, Tr, Allocator>> : std::true_type {};
#endif
// rvalue containers: their elements are moved instead of copied
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_lvalue_reference<CompatibleArrayType>::value
&& !std::is_const<CompatibleArrayType>::value
&& !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value
&& !std::is_same<CompatibleArrayType, std::vector<bool>>::value
&& !is_basic_json<CompatibleArrayType>::value
&& is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value
&& !is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value
&& !is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value
&& !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value
&& !is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
&& !std::is_same<CompatibleArrayType, std_fs::path>::value // has its own to_json
#endif
#if defined(__cpp_lib_char8_t)
&& !is_u8string<CompatibleArrayType>::value // has its own to_json
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleArrayType>::value
#endif
, 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
@@ -7435,21 +7374,6 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
}
#endif
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_lvalue_reference<CompatibleObjectType>::value
&& !std::is_const<CompatibleObjectType>::value
&& !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value
&& is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value
&& !is_basic_json<CompatibleObjectType>::value
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleObjectType>::value
#endif
, 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)
{
@@ -30547,7 +30471,60 @@ public:
#else
static_cast<void>(old_capacity);
#endif
return set_parent(j);
set_parent_plain(j);
return j;
}
/// @brief capacity of the stored object if it is vector-backed (ordered_map), else 0
#if JSON_DIAGNOSTICS
template < typename O = object_t, detail::enable_if_t < detail::has_capacity<O>::value, int > = 0 >
std::size_t object_capacity() const noexcept
{
return m_data.m_value.object->capacity();
}
template < typename O = object_t, detail::enable_if_t < !detail::has_capacity<O>::value, int > = 0 >
std::size_t object_capacity() const noexcept
{
return 0;
}
#else
static constexpr std::size_t object_capacity() noexcept
{
return 0;
}
#endif
/// @brief set parent of an element that was just looked up or inserted into an object
reference set_parent_after_object_emplace(reference j, std::size_t old_capacity)
{
#if JSON_DIAGNOSTICS
if (JSON_HEDLEY_UNLIKELY(object_capacity() != old_capacity))
{
set_parents();
return j;
}
j.m_parent = this;
#else
static_cast<void>(old_capacity);
#endif
return j;
}
void set_parent_plain(reference j) noexcept
{
#if JSON_DIAGNOSTICS
j.m_parent = this;
#else
static_cast<void>(j);
#endif
}
/// @brief set the parent of an element accessed via at(); never moves elements
reference set_parent_at(reference j) noexcept
{
set_parent_plain(j);
return j;
}
reference set_parent(reference j)
@@ -31981,7 +31958,6 @@ public:
copy_leaf_value(other, *this);
}
set_parents();
assert_invariant();
}
@@ -32869,7 +32845,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
@@ -32883,7 +32859,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
@@ -32892,7 +32868,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
@@ -32990,8 +32966,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = object_capacity();
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
return set_parent(result.first->second);
return set_parent_after_object_emplace(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -33041,8 +33018,9 @@ public:
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
const auto old_capacity = object_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_object_emplace(result.first->second, old_capacity);
}
throw_subscript_wrong_type("string");
@@ -34009,8 +33987,9 @@ public:
}
// add the element to the object
const auto old_capacity = object_capacity();
auto res = m_data.m_value.object->insert(val);
set_parent(res.first->second);
set_parent_after_object_emplace(res.first->second, old_capacity);
}
/// @brief add an object to an object
@@ -34086,8 +34065,9 @@ public:
}
// add the element to the array (perfect forwarding)
const auto old_capacity = object_capacity();
auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);
set_parent(res.first->second);
set_parent_after_object_emplace(res.first->second, old_capacity);
// create a result iterator and set iterator to the result of emplace
auto it = begin();
+98
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,99 @@ 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));
}
}
-257
View File
@@ -1,257 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | 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
}