Compare commits

...
Author SHA1 Message Date
Niels Lohmann f674a61318 Compare standard containers without a thread-local depth count
operator== on objects and arrays recursed through the containers' own
operator== and counted the depth in a thread-local variable. Where reading a
thread-local costs a call (macOS), that added about 10% to comparing documents
with many small containers. For the standard containers, walk objects and
arrays directly, compare scalars and strings inline, and pass the depth as an
argument, falling back to compare_iteratively beyond the depth limit. Other
container types keep the previous path.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:48 +02:00
Niels Lohmann 106065e62d Make the value lifecycle inline-friendly
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:15 +02:00
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
9 changed files with 1587 additions and 189 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.
@@ -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)
{
+325 -94
View File
@@ -585,26 +585,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
switch (t)
{
case value_t::object:
{
object = create<object_t>();
break;
}
case value_t::array:
{
array = create<array_t>();
break;
}
case value_t::string:
{
string = create<string_t>("");
break;
}
case value_t::binary:
{
binary = create<binary_t>();
// allocating: kept out of line, so the scalar cases below inline
init_owned(t);
break;
}
@@ -651,6 +637,34 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
}
JSON_HEDLEY_NEVER_INLINE
void init_owned(value_t t)
{
switch (t)
{
case value_t::object:
object = create<object_t>();
break;
case value_t::array:
array = create<array_t>();
break;
case value_t::string:
string = create<string_t>("");
break;
case value_t::binary:
binary = create<binary_t>();
break;
case value_t::null:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::discarded:
default:
break;
}
}
/// constructor for strings
json_value(const string_t& value) : string(create<string_t>(value)) {}
@@ -911,7 +925,18 @@ public:
}
}
// only strings, binaries, arrays and objects own memory; keep the
// check for them inline so destroying a scalar is a compare and a
// branch, and leave the actual work to an out-of-line function
void destroy(value_t t)
{
if (JSON_HEDLEY_UNLIKELY(t == value_t::string || t == value_t::binary || t == value_t::object || t == value_t::array))
{
destroy_owned(t);
}
}
JSON_HEDLEY_NEVER_INLINE void destroy_owned(value_t t)
{
switch (t)
{
@@ -1079,51 +1104,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)
@@ -2546,18 +2584,17 @@ public:
// check of passed value is valid
other.assert_invariant();
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
if (JSON_HEDLEY_LIKELY(m_data.m_type != value_t::object && m_data.m_type != value_t::array
&& m_data.m_type != value_t::string && m_data.m_type != value_t::binary))
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
// null, boolean, numbers, discarded: plain copy of the union
m_data.m_value = other.m_data.m_value;
}
else
{
copy_leaf_value(other, *this);
copy_owning(other);
}
set_parents();
assert_invariant();
}
@@ -2787,6 +2824,22 @@ public:
// value access //
//////////////////
/// out-of-line part of the copy constructor for owning types
JSON_HEDLEY_NEVER_INLINE
void copy_owning(const basic_json& other)
{
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
}
else
{
copy_leaf_value(other, *this);
}
}
/// get a pointer to the value (object)
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
{
@@ -3445,7 +3498,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 +3512,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 +3521,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 +3566,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 +3619,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 +3671,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 +4579,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 +4610,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 +4640,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 +4695,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 +4718,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 +5019,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 +5071,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;
}
}
@@ -5158,6 +5214,20 @@ public:
/// @name lexicographical comparison operators
/// @{
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
// and JSON_IMPLEMENT_SCALAR_FAST
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::null: \
return (null_result); \
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);
// note parentheses around operands are necessary; see
// https://github.com/nlohmann/json/issues/1530
// Mixed signed/unsigned integer comparisons check whether the signed value
@@ -5194,24 +5264,11 @@ public:
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
} \
\
case value_t::null: \
return (null_result); \
\
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
\
case value_t::string: \
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
\
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
\
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
\
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
\
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float); \
\
case value_t::binary: \
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
\
@@ -5255,6 +5312,24 @@ public:
\
return (default_result);
// inline fast path for two values of the same non-owning scalar type; anything
// else falls through to the out-of-line general comparison
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
if (lhs.m_data.m_type == rhs.m_data.m_type) \
{ \
switch (lhs.m_data.m_type) \
{ \
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::object: \
case value_t::array: \
case value_t::string: \
case value_t::binary: \
case value_t::discarded: \
default: \
break; \
} \
}
JSON_PRIVATE_UNLESS_TESTED:
// returns true if:
// - any operand is NaN and the other operand is of number type
@@ -5282,6 +5357,168 @@ public:
return compares_unordered(*this, rhs, inverse);
}
/// @brief whether objects and arrays are the standard containers, whose
/// elements equal_bounded() may therefore compare itself
using equal_bounded_containers = std::integral_constant < bool,
std::is_same<object_t, std::map<StringType, basic_json, default_object_comparator_t,
AllocatorType<std::pair<const StringType, basic_json>>>>::value &&
std::is_same<array_t, std::vector<basic_json, AllocatorType<basic_json>>>::value >;
// out-of-line general comparisons; the operators below handle same-type
// scalars inline and fall through to these
JSON_HEDLEY_NEVER_INLINE
static bool equal_general(const_reference lhs, const_reference rhs) noexcept
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
}
JSON_HEDLEY_NEVER_INLINE
static bool less_general(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
}
#if JSON_HAS_THREE_WAY_COMPARISON
JSON_HEDLEY_NEVER_INLINE
static std::partial_ordering three_way_general(const_reference lhs, const_reference rhs) noexcept // *NOPAD*
{
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
}
#endif
/// @brief compare two values of the same type that holds no children
/// @return 1 if they are equal, 0 if they are not, and -1 if the values are
/// of different types or have children, which this leaves to
/// equal_structured()
JSON_HEDLEY_ALWAYS_INLINE
static int equal_scalar(const_reference lhs, const_reference rhs) noexcept
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
if (lhs.m_data.m_type == rhs.m_data.m_type)
{
switch (lhs.m_data.m_type)
{
case value_t::null:
return 1;
case value_t::boolean:
return lhs.m_data.m_value.boolean == rhs.m_data.m_value.boolean;
case value_t::number_integer:
return lhs.m_data.m_value.number_integer == rhs.m_data.m_value.number_integer;
case value_t::number_unsigned:
return lhs.m_data.m_value.number_unsigned == rhs.m_data.m_value.number_unsigned;
case value_t::number_float:
return lhs.m_data.m_value.number_float == rhs.m_data.m_value.number_float;
case value_t::string:
return *lhs.m_data.m_value.string == *rhs.m_data.m_value.string;
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
}
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return -1;
}
/// @brief compare for equality, walking objects and arrays here instead of
/// in their containers so that the depth is a parameter and needs no
/// thread-local count; only used for the standard containers
static bool equal_bounded(const_reference lhs, const_reference rhs, std::size_t depth) noexcept
{
const int scalar = equal_scalar(lhs, rhs);
return scalar < 0 ? equal_structured(lhs, rhs, depth) : scalar != 0;
}
static bool equal_structured(const_reference lhs, const_reference rhs, std::size_t depth) noexcept
{
const auto type = lhs.m_data.m_type;
if (type != rhs.m_data.m_type || (type != value_t::array && type != value_t::object))
{
return equal_general(lhs, rhs);
}
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{
return compare_iteratively<false>(lhs, rhs, false) == compare_result::equal;
}
if (type == value_t::array)
{
const auto& a = *lhs.m_data.m_value.array;
const auto& b = *rhs.m_data.m_value.array;
if (a.size() != b.size())
{
return false;
}
auto it_b = b.cbegin();
for (auto it_a = a.cbegin(); it_a != a.cend(); ++it_a, ++it_b)
{
const int scalar = equal_scalar(*it_a, *it_b);
if (scalar < 0 ? !equal_structured(*it_a, *it_b, depth + 1) : scalar == 0)
{
return false;
}
}
return true;
}
const auto& a = *lhs.m_data.m_value.object;
const auto& b = *rhs.m_data.m_value.object;
if (a.size() != b.size())
{
return false;
}
auto it_b = b.cbegin();
for (auto it_a = a.cbegin(); it_a != a.cend(); ++it_a, ++it_b)
{
if (!(it_a->first == it_b->first))
{
return false;
}
const int scalar = equal_scalar(it_a->second, it_b->second);
if (scalar < 0 ? !equal_structured(it_a->second, it_b->second, depth + 1) : scalar == 0)
{
return false;
}
}
return true;
}
static bool equal_dispatch(const_reference lhs, const_reference rhs, std::true_type /*standard containers*/) noexcept
{
return equal_bounded(lhs, rhs, 0);
}
static bool equal_dispatch(const_reference lhs, const_reference rhs, std::false_type /*other containers*/) noexcept
{
return equal_general(lhs, rhs);
}
public:
#if JSON_HAS_THREE_WAY_COMPARISON
/// @brief comparison: equal
@@ -5293,11 +5530,11 @@ public:
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
const_reference lhs = *this;
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
}
/// @brief comparison: equal
@@ -5314,13 +5551,8 @@ public:
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
{
const_reference lhs = *this;
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD*
return three_way_general(lhs, rhs);
}
/// @brief comparison: 3-way
@@ -5408,11 +5640,11 @@ public:
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
}
// The friend comparisons with a scalar name the JSON type via decltype of
@@ -5467,11 +5699,8 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
JSON_IMPLEMENT_SCALAR_FAST( <, false)
return less_general(lhs, rhs);
}
/// @brief comparison: less than
@@ -5582,6 +5811,8 @@ public:
#endif
#undef JSON_IMPLEMENT_OPERATOR
#undef JSON_IMPLEMENT_SCALAR_FAST
#undef JSON_IMPLEMENT_SCALAR_CASES
/// @}
+421 -94
View File
@@ -7140,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)
{
@@ -7190,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)
@@ -7327,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
@@ -7400,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)
{
@@ -30229,26 +30325,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
switch (t)
{
case value_t::object:
{
object = create<object_t>();
break;
}
case value_t::array:
{
array = create<array_t>();
break;
}
case value_t::string:
{
string = create<string_t>("");
break;
}
case value_t::binary:
{
binary = create<binary_t>();
// allocating: kept out of line, so the scalar cases below inline
init_owned(t);
break;
}
@@ -30295,6 +30377,34 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
}
JSON_HEDLEY_NEVER_INLINE
void init_owned(value_t t)
{
switch (t)
{
case value_t::object:
object = create<object_t>();
break;
case value_t::array:
array = create<array_t>();
break;
case value_t::string:
string = create<string_t>("");
break;
case value_t::binary:
binary = create<binary_t>();
break;
case value_t::null:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::discarded:
default:
break;
}
}
/// constructor for strings
json_value(const string_t& value) : string(create<string_t>(value)) {}
@@ -30555,7 +30665,18 @@ public:
}
}
// only strings, binaries, arrays and objects own memory; keep the
// check for them inline so destroying a scalar is a compare and a
// branch, and leave the actual work to an out-of-line function
void destroy(value_t t)
{
if (JSON_HEDLEY_UNLIKELY(t == value_t::string || t == value_t::binary || t == value_t::object || t == value_t::array))
{
destroy_owned(t);
}
}
JSON_HEDLEY_NEVER_INLINE void destroy_owned(value_t t)
{
switch (t)
{
@@ -30723,51 +30844,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)
@@ -32190,18 +32324,17 @@ public:
// check of passed value is valid
other.assert_invariant();
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
if (JSON_HEDLEY_LIKELY(m_data.m_type != value_t::object && m_data.m_type != value_t::array
&& m_data.m_type != value_t::string && m_data.m_type != value_t::binary))
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
// null, boolean, numbers, discarded: plain copy of the union
m_data.m_value = other.m_data.m_value;
}
else
{
copy_leaf_value(other, *this);
copy_owning(other);
}
set_parents();
assert_invariant();
}
@@ -32431,6 +32564,22 @@ public:
// value access //
//////////////////
/// out-of-line part of the copy constructor for owning types
JSON_HEDLEY_NEVER_INLINE
void copy_owning(const basic_json& other)
{
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
}
else
{
copy_leaf_value(other, *this);
}
}
/// get a pointer to the value (object)
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
{
@@ -33089,7 +33238,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
@@ -33103,7 +33252,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
@@ -33112,7 +33261,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
@@ -33157,13 +33306,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();
@@ -33210,8 +33359,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");
@@ -33261,8 +33411,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");
@@ -34168,9 +34319,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
}
@@ -34199,9 +34350,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
@@ -34229,8 +34380,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
@@ -34283,9 +34435,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
@@ -34306,8 +34458,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();
@@ -34606,9 +34759,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());
}
}
@@ -34658,9 +34811,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;
}
}
@@ -34802,6 +34954,20 @@ public:
/// @name lexicographical comparison operators
/// @{
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
// and JSON_IMPLEMENT_SCALAR_FAST
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::null: \
return (null_result); \
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);
// note parentheses around operands are necessary; see
// https://github.com/nlohmann/json/issues/1530
// Mixed signed/unsigned integer comparisons check whether the signed value
@@ -34838,24 +35004,11 @@ public:
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
} \
\
case value_t::null: \
return (null_result); \
\
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
\
case value_t::string: \
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
\
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
\
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
\
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
\
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float); \
\
case value_t::binary: \
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
\
@@ -34899,6 +35052,24 @@ public:
\
return (default_result);
// inline fast path for two values of the same non-owning scalar type; anything
// else falls through to the out-of-line general comparison
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
if (lhs.m_data.m_type == rhs.m_data.m_type) \
{ \
switch (lhs.m_data.m_type) \
{ \
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::object: \
case value_t::array: \
case value_t::string: \
case value_t::binary: \
case value_t::discarded: \
default: \
break; \
} \
}
JSON_PRIVATE_UNLESS_TESTED:
// returns true if:
// - any operand is NaN and the other operand is of number type
@@ -34926,6 +35097,168 @@ public:
return compares_unordered(*this, rhs, inverse);
}
/// @brief whether objects and arrays are the standard containers, whose
/// elements equal_bounded() may therefore compare itself
using equal_bounded_containers = std::integral_constant < bool,
std::is_same<object_t, std::map<StringType, basic_json, default_object_comparator_t,
AllocatorType<std::pair<const StringType, basic_json>>>>::value &&
std::is_same<array_t, std::vector<basic_json, AllocatorType<basic_json>>>::value >;
// out-of-line general comparisons; the operators below handle same-type
// scalars inline and fall through to these
JSON_HEDLEY_NEVER_INLINE
static bool equal_general(const_reference lhs, const_reference rhs) noexcept
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
}
JSON_HEDLEY_NEVER_INLINE
static bool less_general(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
}
#if JSON_HAS_THREE_WAY_COMPARISON
JSON_HEDLEY_NEVER_INLINE
static std::partial_ordering three_way_general(const_reference lhs, const_reference rhs) noexcept // *NOPAD*
{
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
}
#endif
/// @brief compare two values of the same type that holds no children
/// @return 1 if they are equal, 0 if they are not, and -1 if the values are
/// of different types or have children, which this leaves to
/// equal_structured()
JSON_HEDLEY_ALWAYS_INLINE
static int equal_scalar(const_reference lhs, const_reference rhs) noexcept
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
if (lhs.m_data.m_type == rhs.m_data.m_type)
{
switch (lhs.m_data.m_type)
{
case value_t::null:
return 1;
case value_t::boolean:
return lhs.m_data.m_value.boolean == rhs.m_data.m_value.boolean;
case value_t::number_integer:
return lhs.m_data.m_value.number_integer == rhs.m_data.m_value.number_integer;
case value_t::number_unsigned:
return lhs.m_data.m_value.number_unsigned == rhs.m_data.m_value.number_unsigned;
case value_t::number_float:
return lhs.m_data.m_value.number_float == rhs.m_data.m_value.number_float;
case value_t::string:
return *lhs.m_data.m_value.string == *rhs.m_data.m_value.string;
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
}
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return -1;
}
/// @brief compare for equality, walking objects and arrays here instead of
/// in their containers so that the depth is a parameter and needs no
/// thread-local count; only used for the standard containers
static bool equal_bounded(const_reference lhs, const_reference rhs, std::size_t depth) noexcept
{
const int scalar = equal_scalar(lhs, rhs);
return scalar < 0 ? equal_structured(lhs, rhs, depth) : scalar != 0;
}
static bool equal_structured(const_reference lhs, const_reference rhs, std::size_t depth) noexcept
{
const auto type = lhs.m_data.m_type;
if (type != rhs.m_data.m_type || (type != value_t::array && type != value_t::object))
{
return equal_general(lhs, rhs);
}
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{
return compare_iteratively<false>(lhs, rhs, false) == compare_result::equal;
}
if (type == value_t::array)
{
const auto& a = *lhs.m_data.m_value.array;
const auto& b = *rhs.m_data.m_value.array;
if (a.size() != b.size())
{
return false;
}
auto it_b = b.cbegin();
for (auto it_a = a.cbegin(); it_a != a.cend(); ++it_a, ++it_b)
{
const int scalar = equal_scalar(*it_a, *it_b);
if (scalar < 0 ? !equal_structured(*it_a, *it_b, depth + 1) : scalar == 0)
{
return false;
}
}
return true;
}
const auto& a = *lhs.m_data.m_value.object;
const auto& b = *rhs.m_data.m_value.object;
if (a.size() != b.size())
{
return false;
}
auto it_b = b.cbegin();
for (auto it_a = a.cbegin(); it_a != a.cend(); ++it_a, ++it_b)
{
if (!(it_a->first == it_b->first))
{
return false;
}
const int scalar = equal_scalar(it_a->second, it_b->second);
if (scalar < 0 ? !equal_structured(it_a->second, it_b->second, depth + 1) : scalar == 0)
{
return false;
}
}
return true;
}
static bool equal_dispatch(const_reference lhs, const_reference rhs, std::true_type /*standard containers*/) noexcept
{
return equal_bounded(lhs, rhs, 0);
}
static bool equal_dispatch(const_reference lhs, const_reference rhs, std::false_type /*other containers*/) noexcept
{
return equal_general(lhs, rhs);
}
public:
#if JSON_HAS_THREE_WAY_COMPARISON
/// @brief comparison: equal
@@ -34937,11 +35270,11 @@ public:
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
const_reference lhs = *this;
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
}
/// @brief comparison: equal
@@ -34958,13 +35291,8 @@ public:
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
{
const_reference lhs = *this;
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD*
return three_way_general(lhs, rhs);
}
/// @brief comparison: 3-way
@@ -35052,11 +35380,11 @@ public:
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
}
// The friend comparisons with a scalar name the JSON type via decltype of
@@ -35111,11 +35439,8 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
JSON_IMPLEMENT_SCALAR_FAST( <, false)
return less_general(lhs, rhs);
}
/// @brief comparison: less than
@@ -35226,6 +35551,8 @@ public:
#endif
#undef JSON_IMPLEMENT_OPERATOR
#undef JSON_IMPLEMENT_SCALAR_FAST
#undef JSON_IMPLEMENT_SCALAR_CASES
/// @}
+155
View File
@@ -19,6 +19,7 @@
#include <cctype>
#include <cstdint>
#include <limits>
#include <map>
#include <string>
#include <utility>
@@ -1149,3 +1150,157 @@ TEST_CASE("operator<=> of binary values with a different subtype does not depend
}
}
#endif
TEST_CASE("comparison of scalars (inline fast path)")
{
const json::number_integer_t neg = -1;
const json::number_integer_t pos = 5;
const json::number_unsigned_t big = (std::numeric_limits<json::number_unsigned_t>::max)();
SECTION("same type")
{
CHECK(json(pos) == json(pos));
CHECK(json(pos) != json(neg));
CHECK(json(neg) < json(pos));
CHECK_FALSE(json(pos) < json(neg));
CHECK_FALSE(json(pos) < json(pos));
CHECK(json(pos) <= json(pos));
CHECK(json(1u) == json(1u));
CHECK(json(1u) < json(2u));
CHECK_FALSE(json(2u) < json(1u));
CHECK(json(1.5) == json(1.5));
CHECK(json(1.5) < json(2.5));
CHECK_FALSE(json(2.5) < json(1.5));
CHECK(json(-0.0) == json(0.0));
}
SECTION("mixed number types")
{
// negative signed vs unsigned must not wrap around
CHECK(json(neg) < json(1u));
CHECK(json(1u) > json(neg));
CHECK(json(neg) != json(big));
CHECK(json(neg) < json(big));
CHECK_FALSE(json(big) < json(neg));
CHECK(json(pos) == json(5u));
CHECK(json(5u) == json(pos));
CHECK(json(pos) < json(6u));
CHECK(json(pos) == json(5.0));
CHECK(json(5.0) == json(pos));
CHECK(json(5u) == json(5.0));
CHECK(json(neg) < json(0.5));
CHECK(json(0.5) < json(1u));
}
SECTION("boolean and null")
{
CHECK(json(true) == json(true));
CHECK(json(true) != json(false));
CHECK(json(false) < json(true));
CHECK_FALSE(json(true) < json(false));
CHECK_FALSE(json(true) < json(true));
CHECK(json(nullptr) == json(nullptr));
CHECK_FALSE(json(nullptr) < json(nullptr));
CHECK(json(nullptr) <= json(nullptr));
CHECK(json(nullptr) < json(false));
CHECK(json(nullptr) != json(false));
CHECK(json(nullptr) != json(0));
}
SECTION("string vs number")
{
CHECK(json("5") != json(5));
CHECK(json(5) != json("5"));
CHECK(json(5) < json("5")); // number < string by type order
CHECK_FALSE(json("5") < json(5));
CHECK(json(1.5) < json(""));
CHECK(json("a") == json("a"));
CHECK(json("a") < json("b"));
}
SECTION("NaN")
{
const json nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
const json one = 1.0;
CHECK_FALSE(nan == nan);
CHECK(nan != nan);
CHECK_FALSE(nan < nan);
CHECK_FALSE(nan <= nan);
CHECK_FALSE(nan >= nan);
CHECK_FALSE(nan > nan);
CHECK_FALSE(nan == one);
CHECK_FALSE(one == nan);
CHECK_FALSE(nan < one);
CHECK_FALSE(one < nan);
CHECK_FALSE(nan <= one);
CHECK_FALSE(one <= nan);
CHECK_FALSE(nan >= one);
CHECK_FALSE(one >= nan);
// NaN against other number types
CHECK_FALSE(nan == json(1));
CHECK_FALSE(json(1) == nan);
CHECK_FALSE(nan < json(1u));
CHECK_FALSE(json(1u) < nan);
#if JSON_HAS_THREE_WAY_COMPARISON
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
#endif
}
SECTION("discarded")
{
const json d(json::value_t::discarded);
CHECK_FALSE(d == d);
CHECK_FALSE(d < d);
CHECK_FALSE(d == json(1));
CHECK_FALSE(json(1) == d);
CHECK_FALSE(d < json(1));
CHECK_FALSE(json(1) < d);
CHECK_FALSE(d == json(nullptr));
CHECK_FALSE(json(nullptr) == d);
#if JSON_HAS_THREE_WAY_COMPARISON
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
#endif
}
SECTION("scalar overloads")
{
CHECK(json(5) == 5);
CHECK(5 == json(5));
CHECK(json(5) < 6);
CHECK(4 < json(5));
CHECK(json(1.5) == 1.5);
CHECK(json(true) == true);
CHECK(json(5) != 6);
CHECK(json(1u) == 1.0);
}
SECTION("structured values still compare")
{
CHECK(json({1, 2}) == json({1, 2}));
CHECK(json({1, 2}) < json({1, 3}));
CHECK(json({{"a", 1}}) == json({{"a", 1}}));
CHECK(json::binary({1, 2}) == json::binary({1, 2}));
CHECK(json(1) < json::array());
CHECK(json::object() < json::array());
}
}
+139
View File
@@ -8,6 +8,9 @@
#include "doctest_compatibility.h"
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -184,3 +187,139 @@ TEST_CASE("other constructors and destructor")
}
}
}
TEST_CASE("copy constructor and destructor across value types")
{
SECTION("copy of a scalar is independent of the source")
{
const json src_null = nullptr;
const json src_bool = true;
const json src_int = -7;
const json src_uint = 7u;
const json src_float = 2.5;
json c_null(src_null);
json c_bool(src_bool);
json c_int(src_int);
json c_uint(src_uint);
json c_float(src_float);
CHECK(c_null.is_null());
CHECK(c_bool == true);
CHECK(c_int == -7);
CHECK(c_uint == 7u);
CHECK(c_float == 2.5);
c_int = 8;
c_float = 3.5;
CHECK(src_int == -7);
CHECK(src_float == 2.5);
}
SECTION("copy of an owning value, then mutate the copy")
{
const json src_string = "hello";
const json src_array = {1, 2, 3};
const json src_object = {{"a", 1}, {"b", {{"c", 2}}}};
const json src_binary = json::binary({1, 2, 3});
json c_string(src_string);
json c_array(src_array);
json c_object(src_object);
json c_binary(src_binary);
CHECK(c_string == src_string);
CHECK(c_array == src_array);
CHECK(c_object == src_object);
CHECK(c_binary == src_binary);
c_string.get_ref<json::string_t&>() += " world";
c_array.push_back(4);
c_object["b"]["c"] = 3;
c_object["d"] = 4;
c_binary.get_binary().push_back(4);
CHECK(src_string == "hello");
CHECK(src_array.size() == 3);
CHECK(src_object["b"]["c"] == 2);
CHECK_FALSE(src_object.contains("d"));
CHECK(src_binary.get_binary().size() == 3);
CHECK(c_string == "hello world");
CHECK(c_array.size() == 4);
CHECK(c_object["b"]["c"] == 3);
CHECK(c_binary.get_binary().size() == 4);
}
SECTION("copy keeps parent pointers valid (JSON_DIAGNOSTICS)")
{
const json src = {{"a", {1, 2}}, {"b", "x"}};
json c(src);
c["a"].push_back(3);
CHECK(c["a"].size() == 3);
CHECK(src["a"].size() == 2);
}
SECTION("vector of mixed types: build, copy, and destroy")
{
{
std::vector<json> v;
for (int i = 0; i < 70; ++i)
{
switch (i % 9)
{
case 0:
v.emplace_back(nullptr);
break;
case 1:
v.emplace_back(i % 2 == 0);
break;
case 2:
v.emplace_back(-i);
break;
case 3:
v.emplace_back(static_cast<json::number_unsigned_t>(i));
break;
case 4:
v.emplace_back(i * 0.5);
break;
case 5:
v.emplace_back(std::string(static_cast<std::size_t>(i), 'x'));
break;
case 6:
v.emplace_back(json::array({i, "s", nullptr}));
break;
case 7:
v.emplace_back(json::object({{"k", i}}));
break;
default:
v.emplace_back(json::binary({1, 2, 3}));
break;
}
}
REQUIRE(v.size() == 70);
std::vector<json> copy(v);
CHECK(copy == v);
// mutating the copy leaves the original alone
copy[5] = "changed";
copy[6].push_back(1);
CHECK(copy != v);
CHECK(v[5] == std::string(5, 'x'));
CHECK(v[6].size() == 3);
v.erase(v.begin(), v.begin() + 10);
CHECK(v.size() == 60);
}
// destroying the discarded and default-constructed values
std::vector<json> w(4);
w.emplace_back(json::value_t::discarded);
w.emplace_back(json::value_t::object);
w.emplace_back(json::value_t::array);
w.emplace_back(json::value_t::string);
w.emplace_back(json::value_t::binary);
CHECK(w.size() == 9);
}
}
+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
}