Compare commits

...
Author SHA1 Message Date
Niels Lohmann cc2dae641d Move elements when converting rvalue containers to JSON
Add to_json overloads for rvalue array-like and object-like containers
that build the array/object from move iterators, so that
json j = std::move(vec) moves the elements instead of copying them.
std::vector<std::string> (100k x 40 chars): 4.7 ms -> 2.3 ms;
std::map<std::string, std::string>: -18 % (keys are const and still copied).

array_t/object_t, std::vector<bool>, std::filesystem::path and
std::u8string keep their dedicated overloads. Lvalues and const rvalues
are copied as before.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 22:25:04 +02:00
Niels Lohmann 4e294f4b18 Clarify that double is only the default floating-point type in the docs (#5830)
Follow-up to review comments on #5825: number_float_t uses double
precision only by default, and the stated limits apply to double.
Also say "default type for X_t" instead of "default value for X_t".

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

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ To store objects in C++, a type is defined by the template parameters explained
### Default type ### Default type
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default value for With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default type for
`array_t` is: `array_t` is:
```cpp ```cpp
+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` `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. 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. - **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), - **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` [`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. or `double` can be used.
@@ -236,7 +244,9 @@ basic_json(basic_json&& other) noexcept;
2. No-throw guarantee: this constructor never throws exceptions. 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 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 `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 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 `to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
JSON value. JSON value.
+1 -1
View File
@@ -23,7 +23,7 @@ To store boolean values in C++, a type is defined by the template parameter `Bo
### Default type ### Default type
With the default values for `BooleanType` (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`. With the default values for `BooleanType` (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`.
### Storage ### Storage
@@ -35,7 +35,7 @@ type to use.
### Default type ### Default type
With the default values for `NumberFloatType` (`double`), the default value for `number_float_t` is `#!cpp double`. With the default values for `NumberFloatType` (`double`), the default type for `number_float_t` is `#!cpp double`.
### Default behavior ### Default behavior
@@ -52,8 +52,9 @@ With the default values for `NumberFloatType` (`double`), the default value for
> interoperability can be achieved by implementations that expect no more precision or range than these provide, in the > interoperability can be achieved by implementations that expect no more precision or range than these provide, in the
> sense that implementations will approximate JSON numbers within the expected precision. > sense that implementations will approximate JSON numbers within the expected precision.
This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default.
numbers smaller than `-1.79769313486232e+308` or greater than `1.79769313486232e+308` are rejected during parsing with Note that with the default `double`, numbers smaller than `-1.79769313486232e+308` or greater than
`1.79769313486232e+308` are rejected during parsing with
[`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can
only be stored by assigning them directly and are serialized to `null`. only be stored by assigning them directly and are serialized to `null`.
@@ -31,7 +31,7 @@ to use.
### Default type ### Default type
With the default values for `NumberIntegerType` (`std::int64_t`), the default value for `number_integer_t` is With the default values for `NumberIntegerType` (`std::int64_t`), the default type for `number_integer_t` is
`#!cpp std::int64_t`. `#!cpp std::int64_t`.
### Default behavior ### Default behavior
@@ -32,7 +32,7 @@ the type to use.
### Default type ### Default type
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default value for `number_unsigned_t` is With the default values for `NumberUnsignedType` (`std::uint64_t`), the default type for `number_unsigned_t` is
`#!cpp std::uint64_t`. `#!cpp std::uint64_t`.
### Default behavior ### Default behavior
+1 -1
View File
@@ -36,7 +36,7 @@ To store objects in C++, a type is defined by the template parameters described
### Default type ### Default type
With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType` With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType`
(`std::allocator`), the default value for `object_t` is: (`std::allocator`), the default type for `object_t` is:
```cpp ```cpp
// until C++14 // until C++14
+1 -1
View File
@@ -32,7 +32,7 @@ JSON class into byte-sized characters during deserialization.
### Default type ### Default type
With the default values for `StringType` (`std::string`), the default value for `string_t` is `#!cpp std::string`. With the default values for `StringType` (`std::string`), the default type for `string_t` is `#!cpp std::string`.
### Encoding ### Encoding
+8 -8
View File
@@ -120,7 +120,7 @@ requirements are collected in [Template Parameter Requirements](template_paramet
### Default type ### Default type
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default value for `object_t` is: With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default type for `object_t` is:
```cpp ```cpp
std::map< std::map<
@@ -177,7 +177,7 @@ json j = m;
### Default type ### Default type
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default value for `array_t` is: With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default type for `array_t` is:
```cpp ```cpp
std::vector< std::vector<
@@ -209,7 +209,7 @@ Unicode values are split by the JSON class into byte-sized characters during des
### Default type ### Default type
With the default values for *StringType* (`std::string`), the default value for `string_t` is `#!cpp std::string`. With the default values for *StringType* (`std::string`), the default type for `string_t` is `#!cpp std::string`.
### Encoding ### Encoding
@@ -234,7 +234,7 @@ String values are stored as pointers in a `basic_json` type. That is, for any ac
### Default type ### Default type
With the default values for *BooleanType* (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`. With the default values for *BooleanType* (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`.
### Storage ### Storage
@@ -252,9 +252,9 @@ This description includes both integer and floating-point numbers. However, C++
### Default types ### Default types
With the default values for *NumberIntegerType* (`std::int64_t`), the default value for `number_integer_t` is `std::int64_t`. With the default values for *NumberIntegerType* (`std::int64_t`), the default type for `number_integer_t` is `std::int64_t`.
With the default values for *NumberUnsignedType* (`std::uint64_t`), the default value for `number_unsigned_t` is `std::uint64_t`. With the default values for *NumberUnsignedType* (`std::uint64_t`), the default type for `number_unsigned_t` is `std::uint64_t`.
With the default values for *NumberFloatType* (`#!cpp double`), the default value for `number_float_t` is `#!cpp double`. With the default values for *NumberFloatType* (`#!cpp double`), the default type for `number_float_t` is `#!cpp double`.
### Default behavior ### Default behavior
@@ -281,7 +281,7 @@ As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_
> This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision. > This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision.
This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`. This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default (see *NumberFloatType* above). Note that with the default `#!cpp double`, numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`.
### Storage ### Storage
+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 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. 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) ## v3.12.0 (2025-04-11)
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible. Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
@@ -167,6 +167,17 @@ struct external_constructor<value_t::array>
j.replace_value(value_t::array, value); 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> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr) 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); 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, template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 > enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
static void construct(BasicJsonType& j, const CompatibleObjectType& obj) static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
@@ -354,6 +376,45 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, 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 #if JSON_HAS_RANGE_VIEW_CONVERSION
template < typename BasicJsonType, typename T, template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
@@ -427,6 +488,21 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
} }
#endif #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> template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{ {
+76
View File
@@ -7114,6 +7114,17 @@ struct external_constructor<value_t::array>
j.replace_value(value_t::array, value); 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> template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr) static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{ {
@@ -7164,6 +7175,17 @@ struct external_constructor<value_t::object>
j.replace_value(value_t::object, value); 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, template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 > enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
static void construct(BasicJsonType& j, const CompatibleObjectType& obj) static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
@@ -7301,6 +7323,45 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, 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 #if JSON_HAS_RANGE_VIEW_CONVERSION
template < typename BasicJsonType, typename T, template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
@@ -7374,6 +7435,21 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
} }
#endif #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> template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{ {
+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
}