Compare commits

...
Author SHA1 Message Date
Niels Lohmann 82250cbe02 Destroy the first levels of a value by recursion
The pointer-reversing walk in destroy_container() avoids the call stack but
moves a value around for every container it visits. Recurse for the first
recursion_depth_limit() levels instead - no allocation, no thread-local, a
bounded frame - and hand anything deeper to the walk. Destroy twitter
0.49 -> 0.36 ms, citm 0.80 -> 0.74 ms (3.12.0: 0.47 / 1.00).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:47:26 +02:00
Niels Lohmann 8b2d29f28b Speed up container destruction, iterator access and diff()
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:52 +02:00
Niels Lohmann 0570c5b06b Construct remaining hot-path exceptions out of line so core functions inline
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:50 +02:00
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
Niels Lohmann 420e9218cd Hint the end when converting an object to a standard container
from_json for map-like containers called emplace() without a hint, which
searches the tree for every key. The keys of the source object are sorted, so
inserting with the end as the hint is constant time, as it was in 3.12.0 via
std::inserter. get<std::map<...>>() of a 25,000 entry object: 4.2 ms -> 3.25 ms
(3.12.0: 3.7 ms).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 11:46:01 +02:00
Niels Lohmann ad4c240e8b Speed up array-to-container from_json
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 10:52:59 +02:00
17 changed files with 2653 additions and 735 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` `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.
+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.
@@ -266,6 +266,22 @@ template<typename ConstructibleArrayType>
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{} {}
// append through push_back() where available; containers without it (such as
// std::set) fall back to insert(end(), ...), which is what std::inserter did
template<typename ConstructibleArrayType, typename ValueType>
auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/)
-> decltype(arr.push_back(std::forward<ValueType>(value)), void())
{
arr.push_back(std::forward<ValueType>(value));
}
template<typename ConstructibleArrayType, typename ValueType>
void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/)
{
using std::end;
arr.insert(end(arr), std::forward<ValueType>(value));
}
template<typename BasicJsonType, typename ConstructibleArrayType, template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t< enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value, std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
@@ -275,17 +291,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
j.template get<typename ConstructibleArrayType::value_type>(), j.template get<typename ConstructibleArrayType::value_type>(),
void()) void())
{ {
using std::end;
ConstructibleArrayType ret; ConstructibleArrayType ret;
from_json_array_reserve(ret, j.size(), priority_tag<1> {}); const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
std::transform(j.begin(), j.end(), from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
std::inserter(ret, end(ret)), [](const BasicJsonType & i) for (const auto& elem : *inner_array)
{ {
// get<BasicJsonType>() returns *this, this won't call a from_json // get<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType // method when value_type is BasicJsonType
return i.template get<typename ConstructibleArrayType::value_type>(); from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {});
}); }
arr = std::move(ret); arr = std::move(ret);
} }
@@ -383,9 +397,13 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret; ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {}); from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
using value_type = typename ConstructibleObjectType::value_type;
for (const auto& p : *inner_object) for (const auto& p : *inner_object)
{ {
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()); // the keys of the source are sorted for the usual object types, so
// hinting the end makes every insertion constant-time (for containers
// that order their keys the same way)
ret.insert(ret.end(), value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()));
} }
obj = std::move(ret); obj = std::move(ret);
} }
@@ -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,66 @@ 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);
} }
// 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 #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 +509,20 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
} }
#endif #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> 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)
{ {
+14 -2
View File
@@ -428,7 +428,7 @@ void templated_json_throw(ExceptionType exception)
@throw type_error.302 always @throw type_error.302 always
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const char* expected, const BasicJsonType& j)
{ {
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j); static_cast<void>(j);
@@ -443,7 +443,7 @@ JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const
@throw type_error always @throw type_error always
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j) JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j)
{ {
static_cast<void>(id_); // unused when JSON_NOEXCEPTION is defined static_cast<void>(id_); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(operation); static_cast<void>(operation);
@@ -451,6 +451,18 @@ JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_,
JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j));
} }
/*!
@brief throws ExceptionType with a fixed message; kept out of line so that callers do not carry the exception construction
*/
template<typename ExceptionType, typename BasicJsonContext>
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_literal(const exception_id id_, const char* what_arg, BasicJsonContext context)
{
static_cast<void>(id_); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(what_arg);
static_cast<void>(context);
JSON_THROW(ExceptionType::create(id_, what_arg, context));
}
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+48 -113
View File
@@ -280,7 +280,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
} }
/// @throw invalid_iterator.214 always /// @throw invalid_iterator.214 always
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_cannot_get_value() const
{ {
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
} }
@@ -294,41 +294,26 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
case value_t::object:
{
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return m_it.object_iterator->second;
}
case value_t::array:
{ {
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return *m_it.array_iterator; return *m_it.array_iterator;
} }
case value_t::null: if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
throw_cannot_get_value();
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{ {
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return m_it.object_iterator->second;
}
// neither array nor object: a primitive value (null has no value to point to)
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin()))
{ {
return *m_object; return *m_object;
} }
throw_cannot_get_value(); throw_cannot_get_value();
} }
}
}
/*! /*!
@brief dereference the iterator @brief dereference the iterator
@@ -338,30 +323,19 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
case value_t::object:
{
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return &(m_it.object_iterator->second);
}
case value_t::array:
{ {
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return &*m_it.array_iterator; return &*m_it.array_iterator;
} }
case value_t::null: if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{ {
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return &(m_it.object_iterator->second);
}
// neither array nor object: a primitive value
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
{ {
return m_object; return m_object;
@@ -369,8 +343,6 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
throw_cannot_get_value(); throw_cannot_get_value();
} }
}
}
/*! /*!
@brief post-increment (it++) @brief post-increment (it++)
@@ -391,35 +363,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{ {
case value_t::object: ++m_it.array_iterator;
return *this;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
std::advance(m_it.object_iterator, 1); std::advance(m_it.object_iterator, 1);
break; return *this;
} }
case value_t::array: // neither array nor object: a primitive value
{
std::advance(m_it.array_iterator, 1);
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
++m_it.primitive_iterator; ++m_it.primitive_iterator;
break;
}
}
return *this; return *this;
} }
@@ -442,35 +399,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{ {
case value_t::object: --m_it.array_iterator;
return *this;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
std::advance(m_it.object_iterator, -1); std::advance(m_it.object_iterator, -1);
break; return *this;
} }
case value_t::array: // neither array nor object: a primitive value
{
std::advance(m_it.array_iterator, -1);
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
--m_it.primitive_iterator; --m_it.primitive_iterator;
break;
}
}
return *this; return *this;
} }
@@ -484,7 +426,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{ {
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
} }
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
@@ -493,25 +435,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return true; return true;
} }
switch (m_object->m_data.m_type) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{ {
case value_t::object:
return (m_it.object_iterator == other.m_it.object_iterator);
case value_t::array:
return (m_it.array_iterator == other.m_it.array_iterator); return (m_it.array_iterator == other.m_it.array_iterator);
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{
return (m_it.object_iterator == other.m_it.object_iterator);
}
// neither array nor object: a primitive value
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
} }
/*! /*!
@@ -533,7 +468,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{ {
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
} }
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
@@ -546,7 +481,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object)); throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
case value_t::array: case value_t::array:
return (m_it.array_iterator < other.m_it.array_iterator); return (m_it.array_iterator < other.m_it.array_iterator);
@@ -602,7 +537,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
case value_t::array: case value_t::array:
{ {
@@ -681,7 +616,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
case value_t::array: case value_t::array:
return m_it.array_iterator - other.m_it.array_iterator; return m_it.array_iterator - other.m_it.array_iterator;
@@ -710,7 +645,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); throw_literal<invalid_iterator>(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object);
case value_t::array: case value_t::array:
return *std::next(m_it.array_iterator, n); return *std::next(m_it.array_iterator, n);
@@ -750,7 +685,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return m_it.object_iterator->first; return m_it.object_iterator->first;
} }
JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
} }
/*! /*!
+604 -243
View File
File diff suppressed because it is too large. Load diff
+7 -1
View File
@@ -92,13 +92,19 @@ private:
/// @brief shared implementation of the const and non-const at() overloads /// @brief shared implementation of the const and non-const at() overloads
/// @throw std::out_of_range if @a key is not found /// @throw std::out_of_range if @a key is not found
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_key_not_found()
{
JSON_THROW(std::out_of_range("key not found"));
}
template<typename Self, typename KeyType> template<typename Self, typename KeyType>
static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second)) static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second))
{ {
const auto it = find_impl(self, key); const auto it = find_impl(self, key);
if (it == self.end()) if (it == self.end())
{ {
JSON_THROW(std::out_of_range("key not found")); throw_key_not_found();
} }
return it->second; return it->second;
} }
+795 -367
View File
File diff suppressed because it is too large. Load diff
+26
View File
@@ -9,7 +9,10 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream> #include <fstream>
#include <array>
#include <deque>
#include <numeric> #include <numeric>
#include <set>
#include <vector> #include <vector>
#include <test_data.hpp> #include <test_data.hpp>
@@ -621,4 +624,27 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000); BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
// conversion of a JSON array into standard containers
template<typename Container>
static void GetArray(benchmark::State& state)
{
json j = json::array();
for (int i = 0; i < 1000; ++i)
{
j.push_back(i);
}
for (auto _ : state)
{
auto c = j.get<Container>();
benchmark::DoNotOptimize(c);
}
state.SetItemsProcessed(state.iterations() * 1000);
}
BENCHMARK_TEMPLATE(GetArray, std::vector<int>);
BENCHMARK_TEMPLATE(GetArray, std::deque<int>);
BENCHMARK_TEMPLATE(GetArray, std::set<int>);
BENCHMARK_TEMPLATE(GetArray, std::array<int, 1000>);
BENCHMARK_MAIN(); BENCHMARK_MAIN();
+155
View File
@@ -19,6 +19,7 @@
#include <cctype> #include <cctype>
#include <cstdint> #include <cstdint>
#include <limits>
#include <map> #include <map>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -1149,3 +1150,157 @@ TEST_CASE("operator<=> of binary values with a different subtype does not depend
} }
} }
#endif #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());
}
}
+193
View File
@@ -8,6 +8,9 @@
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <string>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
@@ -184,3 +187,193 @@ 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);
}
}
TEST_CASE_TEMPLATE("destructor of nested containers", Json, nlohmann::json, nlohmann::ordered_json)
{
SECTION("empty containers")
{
{
Json j = Json::array();
}
{
Json j = Json::object();
}
{
Json j = Json::array({Json::array(), Json::object(), Json::array({Json::array()})});
}
}
SECTION("leaves mixed with nested containers")
{
Json j = Json::object();
j["leaf"] = 1;
j["str"] = "text";
j["empty_a"] = Json::array();
j["empty_o"] = Json::object();
j["arr"] = Json::array({1, "a", Json::array({1, 2}), Json::object({{"k", Json::array({Json::object()})}}), nullptr});
j["obj"] = Json::object({{"x", Json::object({{"y", Json::array({1, Json::object({{"z", 1}})})}})}, {"w", 2}});
Json copy = j;
CHECK(copy == j);
j = Json::array({std::move(copy), j, 3, Json::array()});
}
SECTION("deep nesting")
{
for (int kind = 0; kind < 3; ++kind)
{
Json j = 1;
for (int i = 0; i < 5000; ++i)
{
switch (kind)
{
case 0:
j = Json::array({std::move(j)});
break;
case 1:
j = Json::object({{"k", std::move(j)}});
break;
default:
// siblings next to the nested child
j = (i % 2 == 0) ? Json::array({1, Json::array(), std::move(j)}) : Json::object({{"a", 1}, {"b", Json::object()}, {"c", std::move(j)}});
break;
}
}
}
}
}
+174
View File
@@ -13,9 +13,11 @@
#endif #endif
#define JSON_DIAGNOSTICS 1 #define JSON_DIAGNOSTICS 1
#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
using nlohmann::ordered_json;
#include <map> #include <map>
#include <unordered_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); 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");
}
}
+107
View File
@@ -10,6 +10,8 @@
#define JSON_TESTS_PRIVATE #define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <string>
#include <vector>
using nlohmann::json; using nlohmann::json;
TEST_CASE("iterators 1") TEST_CASE("iterators 1")
@@ -1628,3 +1630,108 @@ TEST_CASE("iterators 1")
} }
} }
} }
TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, nlohmann::ordered_json)
{
SECTION("array: -- and rbegin/rend")
{
Json j = {1, 2, 3, 4};
auto it = j.end();
std::vector<int> seen;
while (it != j.begin())
{
--it;
seen.push_back(it->template get<int>());
}
CHECK(seen == std::vector<int> {4, 3, 2, 1});
auto it2 = j.end();
auto old = it2--;
CHECK(old == j.end());
CHECK(*it2 == 4);
seen.clear();
for (auto r = j.rbegin(); r != j.rend(); ++r)
{
seen.push_back(r->template get<int>());
}
CHECK(seen == std::vector<int> {4, 3, 2, 1});
seen.clear();
for (auto r = j.crbegin(); r != j.crend(); ++r)
{
seen.push_back(*r);
}
CHECK(seen == std::vector<int> {4, 3, 2, 1});
}
SECTION("object: -- and rbegin/rend")
{
Json j = {{"a", 1}, {"b", 2}, {"c", 3}};
// order of keys depends on the object type; use the forward order as reference
std::vector<std::string> fwd;
for (auto it = j.begin(); it != j.end(); ++it)
{
fwd.push_back(it.key());
}
std::vector<std::string> rev(fwd.rbegin(), fwd.rend());
std::vector<std::string> seen;
auto it = j.end();
while (it != j.begin())
{
--it;
seen.push_back(it.key());
}
CHECK(seen == rev);
auto it2 = j.end();
auto old = it2--;
CHECK(old == j.end());
CHECK(it2.key() == rev.front());
seen.clear();
for (auto r = j.rbegin(); r != j.rend(); ++r)
{
seen.push_back(r.key());
}
CHECK(seen == rev);
}
SECTION("object: operator-> reads and writes the mapped value")
{
Json j = {{"a", 1}, {"s", "str"}};
auto it = j.find("s");
REQUIRE(it != j.end());
CHECK(it->is_string());
CHECK(it->size() == 1);
*it = Json::array({1, 2});
CHECK(it->is_array());
it->push_back(3);
CHECK(j["s"].size() == 3);
const Json& cj = j;
auto cit = cj.find("a");
REQUIRE(cit != cj.end());
CHECK(cit->is_number_integer());
CHECK(cit->template get<int>() == 1);
}
SECTION("primitives: -- and ->")
{
Json j = 42;
auto it = j.end();
--it;
CHECK(it == j.begin());
CHECK(it->template get<int>() == 42);
}
SECTION("comparing iterators of different containers throws")
{
Json a = {1, 2};
Json b = {1, 2};
CHECK_THROWS_WITH_AS(a.begin() == b.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", nlohmann::json::invalid_iterator);
CHECK_THROWS_WITH_AS(a.begin() < b.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", nlohmann::json::invalid_iterator);
}
}
+37
View File
@@ -2066,3 +2066,40 @@ TEST_CASE("JSON patch - every operation on ordered_json")
} }
} }
} }
TEST_CASE_TEMPLATE("JSON patch - diff round trip on hand-made documents", Json, nlohmann::json, nlohmann::ordered_json)
{
const auto roundtrip = [](const Json & a, const Json & b)
{
CAPTURE(a.dump())
CAPTURE(b.dump())
const Json p = Json::diff(a, b);
CHECK(a.patch(p) == b);
CHECK(Json::diff(b, b).empty());
};
SECTION("reordered keys")
{
const Json a = Json::parse(R"({"x":1,"y":2,"z":{"p":1,"q":2}})");
const Json b = Json::parse(R"({"z":{"q":2,"p":1},"y":2,"x":1})");
roundtrip(a, b);
roundtrip(b, a);
}
SECTION("nested add, remove and replace")
{
const Json a = Json::parse(R"({"a":[1,2,{"k":"v","gone":true}],"b":{"c":{"d":1}},"e":"s"})");
const Json b = Json::parse(R"({"a":[1,3,{"k":"w","new":[]}],"b":{"c":{"d":[1,2]}},"f":null})");
roundtrip(a, b);
roundtrip(b, a);
}
SECTION("arrays of different length and type changes")
{
roundtrip(Json::parse("[1,2,3,4,5]"), Json::parse("[1,3]"));
roundtrip(Json::parse("[1]"), Json::parse("[1,[2],{\"a\":3}]"));
roundtrip(Json::parse("[]"), Json::parse("{}"));
roundtrip(Json::parse("{\"a\":[]}"), Json::parse("{\"a\":{}}"));
roundtrip(Json(1), Json::parse("[1]"));
}
}
+94
View File
@@ -0,0 +1,94 @@
// __ _____ _____ _____
// __| | __| | | | 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;
// The exceptions in hot core functions are constructed out of line. These tests pin the
// exact what() texts of the sites that moved, so the refactoring cannot change them.
#define CHECK_MSG(expr, msg) CHECK_THROWS_WITH(expr, msg)
TEST_CASE("exception messages of out-of-line throw sites")
{
json arr = {1, 2, 3};
json arr2 = {4, 5};
json obj = {{"a", 1}, {"b", 2}};
json obj2 = {{"c", 3}};
json num = 1;
SECTION("iterators")
{
CHECK_MSG((void)(arr.begin() == arr2.begin()), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
CHECK_MSG((void)(arr.begin() < arr2.begin()), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
CHECK_MSG((void)(obj.begin() < obj.end()), "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
CHECK_MSG(obj.begin() += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
CHECK_MSG((void)(obj.begin() - 1), "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
CHECK_MSG((void)obj.begin()[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators");
CHECK_MSG((void)arr.begin().key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
json null;
CHECK_MSG((void)*null.end(), "[json.exception.invalid_iterator.214] cannot get value");
}
SECTION("constructors")
{
CHECK_MSG(json(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible");
CHECK_MSG(json(num.begin() + 1, num.end() + 1), "[json.exception.invalid_iterator.204] iterators out of range");
CHECK_MSG(json(json::initializer_list_t{1, 2}, false, json::value_t::object), "[json.exception.type_error.301] cannot create object from initializer list");
}
SECTION("get_ref and operator[]")
{
CHECK_MSG((void)num.get_ref<std::string&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number");
CHECK_MSG((void)num[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number");
}
SECTION("at")
{
const json carr = arr;
const json cobj = obj;
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
CHECK_MSG((void)obj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)cobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)obj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
nlohmann::ordered_json oobj = {{"a", 1}};
const nlohmann::ordered_json coobj = oobj;
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
}
SECTION("erase")
{
CHECK_MSG(arr.erase(arr2.begin()), "[json.exception.invalid_iterator.202] iterator does not fit current value");
CHECK_MSG(num.erase(num.end()), "[json.exception.invalid_iterator.205] iterator out of range");
CHECK_MSG(arr.erase(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.203] iterators do not fit current value");
CHECK_MSG(num.erase(num.begin() + 1, num.end()), "[json.exception.invalid_iterator.204] iterators out of range");
CHECK_MSG(arr.erase(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
}
SECTION("insert")
{
CHECK_MSG(arr.insert(arr2.begin(), 1), "[json.exception.invalid_iterator.202] iterator does not fit current value");
CHECK_MSG(arr.insert(arr2.begin(), 2, 1), "[json.exception.invalid_iterator.202] iterator does not fit current value");
CHECK_MSG(arr.insert(arr2.begin(), arr2.begin(), arr2.end()), "[json.exception.invalid_iterator.202] iterator does not fit current value");
CHECK_MSG(arr.insert(arr.begin(), arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
CHECK_MSG(arr.insert(arr.begin(), arr.begin(), arr.end()), "[json.exception.invalid_iterator.211] passed iterators may not belong to container");
CHECK_MSG(arr.insert(arr.begin(), obj.begin(), obj.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to arrays");
CHECK_MSG(arr.insert(arr2.begin(), {1, 2}), "[json.exception.invalid_iterator.202] iterator does not fit current value");
CHECK_MSG(obj.insert(obj2.begin(), arr.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
CHECK_MSG(obj.insert(arr.begin(), arr.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to objects");
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
}
}
+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
}