mirror of
https://github.com/nlohmann/json.git
synced 2026-08-29 04:17:32 +00:00
Relax the ArrayType and ObjectType requirements
Two requirements forced users of otherwise suitable containers to write a wrapper, and neither was load-bearing. array_t::capacity() was read in push_back(), emplace_back(), operator+=(), and operator[](size_type), but set_parent() only looks at the value under JSON_DIAGNOSTICS; without diagnostics it was computed and discarded. Read it through array_capacity(), which reports unknown_size() when diagnostics are off or when the array type has no capacity() at all, and treat an unknown capacity as "the elements may have moved" so the parent pointers are refreshed conservatively. std::deque now works as ArrayType, in both builds, and capacity() is no longer named at all in a default build. Since the capacity is now only meaningful for array insertions, it moves out of set_parent() into set_parent_after_array_insert(). basic_json::erase(iterator) assigned the object's erase() return value, which requires the container to return the following iterator. Abseil's hash maps return void to avoid computing a successor the caller may not need. Detect that and compute the successor before erasing; containers that return an iterator, including the vector-backed ordered_map where a precomputed successor would be wrong, keep the existing path. Together these leave an Abseil hash map needing only an alias that restores the template argument order, and no adapter at all for std::deque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
co-authored by
Claude Opus 5
parent
96806af2dc
commit
5d93f35463
@@ -14,9 +14,9 @@ To store objects in C++, a type is defined by the template parameters explained
|
||||
## Template parameters
|
||||
|
||||
`ArrayType`
|
||||
: container type to store arrays. It must be a vector-like container: the library uses `operator[]`, `at()`,
|
||||
`resize()`, and `capacity()`, and requires random-access iterators. `#!cpp std::deque` and `#!cpp std::list` do
|
||||
not provide `capacity()` and therefore cannot be used as-is -- see
|
||||
: container type to store arrays. It must be a vector-like container: the library uses `operator[]`, `at()`, and
|
||||
`resize()`, and requires random-access iterators. `#!cpp std::vector` and `#!cpp std::deque` qualify;
|
||||
`#!cpp std::list` does not. See
|
||||
[Template Parameter Requirements](../../features/types/template_parameters.md#arraytype) for the full list of
|
||||
requirements.
|
||||
|
||||
@@ -70,3 +70,4 @@ Arrays are stored as pointers in a `basic_json` type. That is, for any access to
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Made `capacity()` optional, so that array types such as `#!cpp std::deque` can be used, in version 3.13.0.
|
||||
|
||||
@@ -126,3 +126,4 @@ the object is silently converted as an array of key-value pairs, which is incorr
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Allowed object types whose `erase(iterator)` returns `#!cpp void` in version 3.13.0.
|
||||
|
||||
@@ -29,7 +29,7 @@ Requirements are split into two groups:
|
||||
| Template parameter | Default | Notable substitutes |
|
||||
|-------------------------------------------------------------------|-----------------------------------|------------------------------------------------------------------------|
|
||||
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | vector-like containers only |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
|
||||
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
|
||||
| [`BooleanType`](#booleantype) | `bool` | none worth using |
|
||||
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
|
||||
@@ -79,7 +79,8 @@ i.e., the template arguments follow the order and meaning of `std::map`.
|
||||
- Constructors: default, copy, move, and from an iterator range `(first, last)`.
|
||||
- Member functions `begin()`, `end()`, `cbegin()`, `cend()`, `empty()`, `size()`, `max_size()`, `clear()`,
|
||||
`find(key)`, `count(key)`, `emplace(key, value)`, `insert(value_type)`, `insert(first, last)`, `operator[](key)`,
|
||||
`erase(iterator)`, `erase(first, last)`, and `erase(key)`.
|
||||
`erase(iterator)`, `erase(first, last)`, and `erase(key)`. `erase(iterator)` may return the following iterator or
|
||||
`#!cpp void`; in the latter case the library computes the successor itself, before erasing.
|
||||
- `emplace` and `insert(value_type)` must return `#!cpp std::pair<iterator, bool>` and must have **unique-key**
|
||||
semantics; multimaps cannot be used.
|
||||
- The type must be swappable (via `std::swap` or an ADL `swap`).
|
||||
@@ -123,33 +124,17 @@ similar containers are integrated; see [Object Order](../object_order.md).
|
||||
#### Abseil hash maps
|
||||
|
||||
`absl::flat_hash_map` and `absl::node_hash_map` tolerate an incomplete value type, but they take a hash function as
|
||||
their third template argument and their `erase(iterator)` returns `#!cpp void` rather than the following iterator. An
|
||||
adapter that fixes both makes them usable:
|
||||
their third template argument. The same adapter as for `#!cpp std::unordered_map` makes them usable:
|
||||
|
||||
```cpp
|
||||
template<template<class, class, class, class, class> class Map>
|
||||
struct absl_object
|
||||
template<class Key, class T, class IgnoredCompare, class Allocator>
|
||||
struct flat_hash_object
|
||||
: absl::flat_hash_map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>
|
||||
{
|
||||
template<class Key, class T, class IgnoredCompare, class Allocator>
|
||||
struct type : Map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>
|
||||
{
|
||||
using base_t = Map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>;
|
||||
using base_t::base_t;
|
||||
using iterator = typename base_t::iterator;
|
||||
using base_t::erase;
|
||||
|
||||
iterator erase(iterator pos)
|
||||
{
|
||||
iterator next = std::next(pos);
|
||||
base_t::erase(pos);
|
||||
return next;
|
||||
}
|
||||
};
|
||||
using base_t = absl::flat_hash_map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>;
|
||||
using base_t::base_t;
|
||||
};
|
||||
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
using flat_hash_object = typename absl_object<absl::flat_hash_map>::template type<Key, T, Compare, Allocator>;
|
||||
|
||||
using flat_hash_json = nlohmann::basic_json<flat_hash_object>;
|
||||
```
|
||||
|
||||
@@ -204,8 +189,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
- Constructors: default, copy, move, from an iterator range `(first, last)`, and from `(count, value)`.
|
||||
- Member functions `begin()`, `end()`, `cbegin()`, `cend()`, `empty()`, `size()`, `max_size()`, `clear()`,
|
||||
`operator[](size_type)`, `at(size_type)`, `back()`, `push_back()`, `emplace_back()`, `pop_back()`, `resize()`,
|
||||
`insert()` (single element, count, range, and initializer list), `erase(pos)`, `erase(first, last)`, and
|
||||
**`capacity()`**.
|
||||
`insert()` (single element, count, range, and initializer list), `erase(pos)`, and `erase(first, last)`.
|
||||
- `iterator` must be default-constructible, and it as well as the type returned by `cbegin()`/`cend()` must satisfy
|
||||
[LegacyRandomAccessIterator](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator).
|
||||
A `#!cpp static_assert` only checks for
|
||||
@@ -215,23 +199,21 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
[`basic_json::iterator`](../../api/basic_json/begin.md) require random access.
|
||||
- The type must be swappable and provide the comparison operators `==`, `!=`, `<`, `<=`, `>`, `>=` (or `<=>`).
|
||||
|
||||
!!! note "`capacity()` is required unconditionally"
|
||||
!!! note "`capacity()` is optional"
|
||||
|
||||
[`push_back`](../../api/basic_json/push_back.md), [`emplace_back`](../../api/basic_json/emplace_back.md),
|
||||
[`operator+=`](../../api/basic_json/operator+=.md), and
|
||||
[`operator[]`](../../api/basic_json/operator%5B%5D.md) with an array index read `array_t::capacity()` to
|
||||
detect reallocations, regardless of whether [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md) is enabled.
|
||||
Consequently `#!cpp std::deque` and `#!cpp std::list` cannot be used as `ArrayType` as-is. A `std::deque` becomes
|
||||
usable when wrapped in a type that adds a `capacity()` member function; `#!cpp std::list` additionally lacks
|
||||
`operator[]` and random-access iterators and cannot be used at all.
|
||||
With [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md) enabled, the library reads `array_t::capacity()`
|
||||
to find out whether adding an element reallocated the array and moved its elements, which would invalidate the
|
||||
parent pointers. An array type without a `capacity()` member function is handled conservatively: the parent
|
||||
pointers of all elements are refreshed after every insertion, which makes adding *n* elements cost O(*n*²). Only
|
||||
diagnostics builds pay this; without them `capacity()` is never called.
|
||||
|
||||
### Compatible containers
|
||||
|
||||
| Container | Support |
|
||||
|-------------------------------|----------------------------------------------------------------------------------|
|
||||
| `#!cpp std::vector` (default) | full |
|
||||
| `#!cpp std::deque` | only when wrapped in a type that adds a `capacity()` member function |
|
||||
| `#!cpp std::list` | not usable; no `operator[]`, no `capacity()`, and no random-access iterators |
|
||||
| `#!cpp std::deque` | full; keeps references valid while the array grows, but see the note on `capacity()` above |
|
||||
| `#!cpp std::list` | not usable; no `operator[]` and no random-access iterators |
|
||||
| `absl::InlinedVector` | not usable; requires a complete value type |
|
||||
| `absl::FixedArray` | not usable; the size is fixed at construction |
|
||||
|
||||
|
||||
@@ -779,6 +779,13 @@ using has_erase_with_key_type = typename std::conditional <
|
||||
std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
template<typename T>
|
||||
using detect_capacity = decltype(std::declval<const T&>().capacity());
|
||||
|
||||
// type trait to check if a type has a capacity() member function
|
||||
template<typename T>
|
||||
struct has_capacity : std::integral_constant<bool, is_detected<detect_capacity, T>::value> {};
|
||||
|
||||
// a naive helper to check if a type is an ordered_map (exploits the fact that
|
||||
// ordered_map inherits capacity() from std::vector)
|
||||
template <typename T>
|
||||
|
||||
+75
-20
@@ -783,21 +783,76 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return it;
|
||||
}
|
||||
|
||||
reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size())
|
||||
/// @brief erase an element from the object and return the following one
|
||||
/// Not every map returns an iterator from erase(iterator): some containers
|
||||
/// (e.g., Abseil's hash maps) return void to avoid computing a successor
|
||||
/// the caller may not need. Compute it before erasing for those.
|
||||
template < typename It, detail::enable_if_t <
|
||||
!std::is_void<decltype(std::declval<object_t&>().erase(std::declval<It>()))>::value, int > = 0 >
|
||||
typename object_t::iterator erase_from_object(It pos)
|
||||
{
|
||||
return m_data.m_value.object->erase(pos);
|
||||
}
|
||||
|
||||
template < typename It, detail::enable_if_t <
|
||||
std::is_void<decltype(std::declval<object_t&>().erase(std::declval<It>()))>::value, int > = 0 >
|
||||
typename object_t::iterator erase_from_object(It pos)
|
||||
{
|
||||
typename object_t::iterator next = std::next(pos);
|
||||
m_data.m_value.object->erase(pos);
|
||||
return next;
|
||||
}
|
||||
|
||||
/// @brief the capacity of the stored array, 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".
|
||||
#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
|
||||
{
|
||||
return m_data.m_value.array->capacity();
|
||||
}
|
||||
|
||||
template < typename A = array_t, detail::enable_if_t < !detail::has_capacity<A>::value, int > = 0 >
|
||||
std::size_t array_capacity() const noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
}
|
||||
#else
|
||||
static constexpr std::size_t array_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
|
||||
/// insertion
|
||||
reference set_parent_after_array_insert(reference j, std::size_t old_capacity)
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
if (old_capacity != detail::unknown_size())
|
||||
// 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))
|
||||
{
|
||||
// see https://github.com/nlohmann/json/issues/2838
|
||||
JSON_ASSERT(type() == value_t::array);
|
||||
if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))
|
||||
{
|
||||
// capacity has changed: update all parents
|
||||
set_parents();
|
||||
return j;
|
||||
}
|
||||
// the capacity has changed, or the array 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);
|
||||
}
|
||||
|
||||
reference set_parent(reference j)
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
// ordered_json uses a vector internally, so pointers could have
|
||||
// been invalidated; see https://github.com/nlohmann/json/issues/2962
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
@@ -816,7 +871,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
j.m_parent = this;
|
||||
#else
|
||||
static_cast<void>(j);
|
||||
static_cast<void>(old_capacity);
|
||||
#endif
|
||||
return j;
|
||||
}
|
||||
@@ -2147,12 +2201,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#if JSON_DIAGNOSTICS
|
||||
// remember array size & capacity before resizing
|
||||
const auto old_size = m_data.m_value.array->size();
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
#endif
|
||||
m_data.m_value.array->resize(idx + 1);
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))
|
||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
||||
|| array_capacity() != old_capacity))
|
||||
{
|
||||
// capacity has changed: update all parents
|
||||
set_parents();
|
||||
@@ -2543,7 +2598,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator);
|
||||
result.m_it.object_iterator = erase_from_object(pos.m_it.object_iterator);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3173,9 +3228,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
// add the element to the array (move semantics)
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
m_data.m_value.array->push_back(std::move(val));
|
||||
set_parent(m_data.m_value.array->back(), old_capacity);
|
||||
set_parent_after_array_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
|
||||
}
|
||||
|
||||
@@ -3206,9 +3261,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
// add the element to the array
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
m_data.m_value.array->push_back(val);
|
||||
set_parent(m_data.m_value.array->back(), old_capacity);
|
||||
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an array
|
||||
@@ -3294,9 +3349,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
// add the element to the array (perfect forwarding)
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
m_data.m_value.array->emplace_back(std::forward<Args>(args)...);
|
||||
return set_parent(m_data.m_value.array->back(), old_capacity);
|
||||
return set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an object if key does not exist
|
||||
|
||||
@@ -4542,6 +4542,13 @@ using has_erase_with_key_type = typename std::conditional <
|
||||
std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
template<typename T>
|
||||
using detect_capacity = decltype(std::declval<const T&>().capacity());
|
||||
|
||||
// type trait to check if a type has a capacity() member function
|
||||
template<typename T>
|
||||
struct has_capacity : std::integral_constant<bool, is_detected<detect_capacity, T>::value> {};
|
||||
|
||||
// a naive helper to check if a type is an ordered_map (exploits the fact that
|
||||
// ordered_map inherits capacity() from std::vector)
|
||||
template <typename T>
|
||||
@@ -22192,21 +22199,76 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return it;
|
||||
}
|
||||
|
||||
reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size())
|
||||
/// @brief erase an element from the object and return the following one
|
||||
/// Not every map returns an iterator from erase(iterator): some containers
|
||||
/// (e.g., Abseil's hash maps) return void to avoid computing a successor
|
||||
/// the caller may not need. Compute it before erasing for those.
|
||||
template < typename It, detail::enable_if_t <
|
||||
!std::is_void<decltype(std::declval<object_t&>().erase(std::declval<It>()))>::value, int > = 0 >
|
||||
typename object_t::iterator erase_from_object(It pos)
|
||||
{
|
||||
return m_data.m_value.object->erase(pos);
|
||||
}
|
||||
|
||||
template < typename It, detail::enable_if_t <
|
||||
std::is_void<decltype(std::declval<object_t&>().erase(std::declval<It>()))>::value, int > = 0 >
|
||||
typename object_t::iterator erase_from_object(It pos)
|
||||
{
|
||||
typename object_t::iterator next = std::next(pos);
|
||||
m_data.m_value.object->erase(pos);
|
||||
return next;
|
||||
}
|
||||
|
||||
/// @brief the capacity of the stored array, 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".
|
||||
#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
|
||||
{
|
||||
return m_data.m_value.array->capacity();
|
||||
}
|
||||
|
||||
template < typename A = array_t, detail::enable_if_t < !detail::has_capacity<A>::value, int > = 0 >
|
||||
std::size_t array_capacity() const noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
}
|
||||
#else
|
||||
static constexpr std::size_t array_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
|
||||
/// insertion
|
||||
reference set_parent_after_array_insert(reference j, std::size_t old_capacity)
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
if (old_capacity != detail::unknown_size())
|
||||
// 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))
|
||||
{
|
||||
// see https://github.com/nlohmann/json/issues/2838
|
||||
JSON_ASSERT(type() == value_t::array);
|
||||
if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))
|
||||
{
|
||||
// capacity has changed: update all parents
|
||||
set_parents();
|
||||
return j;
|
||||
}
|
||||
// the capacity has changed, or the array 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);
|
||||
}
|
||||
|
||||
reference set_parent(reference j)
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
// ordered_json uses a vector internally, so pointers could have
|
||||
// been invalidated; see https://github.com/nlohmann/json/issues/2962
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
@@ -22225,7 +22287,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
j.m_parent = this;
|
||||
#else
|
||||
static_cast<void>(j);
|
||||
static_cast<void>(old_capacity);
|
||||
#endif
|
||||
return j;
|
||||
}
|
||||
@@ -23556,12 +23617,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#if JSON_DIAGNOSTICS
|
||||
// remember array size & capacity before resizing
|
||||
const auto old_size = m_data.m_value.array->size();
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
#endif
|
||||
m_data.m_value.array->resize(idx + 1);
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))
|
||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
||||
|| array_capacity() != old_capacity))
|
||||
{
|
||||
// capacity has changed: update all parents
|
||||
set_parents();
|
||||
@@ -23952,7 +24014,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator);
|
||||
result.m_it.object_iterator = erase_from_object(pos.m_it.object_iterator);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -24582,9 +24644,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
// add the element to the array (move semantics)
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
m_data.m_value.array->push_back(std::move(val));
|
||||
set_parent(m_data.m_value.array->back(), old_capacity);
|
||||
set_parent_after_array_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
|
||||
}
|
||||
|
||||
@@ -24615,9 +24677,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
// add the element to the array
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
m_data.m_value.array->push_back(val);
|
||||
set_parent(m_data.m_value.array->back(), old_capacity);
|
||||
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an array
|
||||
@@ -24703,9 +24765,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
// add the element to the array (perfect forwarding)
|
||||
const auto old_capacity = m_data.m_value.array->capacity();
|
||||
const auto old_capacity = array_capacity();
|
||||
m_data.m_value.array->emplace_back(std::forward<Args>(args)...);
|
||||
return set_parent(m_data.m_value.array->back(), old_capacity);
|
||||
return set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an object if key does not exist
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// std::deque has no capacity() member function, which the library only needs
|
||||
// to detect a reallocation for JSON_DIAGNOSTICS
|
||||
using deque_json = nlohmann::basic_json<std::map, std::deque>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("array type without capacity()")
|
||||
{
|
||||
SECTION("adding elements")
|
||||
{
|
||||
deque_json j = deque_json::array();
|
||||
j.push_back(1);
|
||||
j.push_back("two");
|
||||
j.emplace_back(3);
|
||||
j += 4;
|
||||
|
||||
CHECK(j.size() == 4);
|
||||
CHECK(j == deque_json({1, "two", 3, 4}));
|
||||
CHECK(j.back() == 4);
|
||||
CHECK(j.front() == 1);
|
||||
}
|
||||
|
||||
SECTION("accessing and modifying elements")
|
||||
{
|
||||
auto j = deque_json::parse(R"([1,2,3])");
|
||||
|
||||
CHECK(j[1] == 2);
|
||||
CHECK(j.at(2) == 3);
|
||||
|
||||
// growing through operator[] fills up with null values
|
||||
j[5] = 6;
|
||||
CHECK(j.size() == 6);
|
||||
CHECK(j[4].is_null());
|
||||
CHECK(j[5] == 6);
|
||||
|
||||
j.erase(0);
|
||||
CHECK(j == deque_json({2, 3, nullptr, nullptr, 6}));
|
||||
|
||||
auto it = j.erase(j.begin());
|
||||
CHECK(*it == 3);
|
||||
|
||||
j.insert(j.begin(), 1);
|
||||
CHECK(j.front() == 1);
|
||||
}
|
||||
|
||||
SECTION("serialization and deserialization")
|
||||
{
|
||||
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
||||
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
||||
CHECK(deque_json::parse(j.dump()) == j);
|
||||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||||
|
||||
// empty containers are flattened to null and cannot be restored
|
||||
const auto nested = deque_json::parse(R"({"a":[1,[2,3]]})");
|
||||
CHECK(nested.flatten().unflatten() == nested);
|
||||
}
|
||||
|
||||
SECTION("references stay valid while the array grows")
|
||||
{
|
||||
deque_json j = deque_json::array();
|
||||
j.push_back(1);
|
||||
auto& first = j[0];
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
j.push_back(i);
|
||||
}
|
||||
CHECK(&first == &j[0]);
|
||||
CHECK(first == 1);
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,76 @@ struct unordered_map_object
|
||||
|
||||
using unordered_json = nlohmann::basic_json<unordered_map_object>;
|
||||
|
||||
// An ObjectType whose erase(iterator) returns void rather than the following
|
||||
// iterator, as for instance Abseil's hash maps do
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
||||
{
|
||||
using base_t = std::map<Key, T, Compare, Allocator>;
|
||||
using base_t::base_t;
|
||||
using iterator = typename base_t::iterator;
|
||||
using base_t::erase;
|
||||
|
||||
void erase(iterator pos)
|
||||
{
|
||||
base_t::erase(pos);
|
||||
}
|
||||
};
|
||||
|
||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("object type whose erase() returns void")
|
||||
{
|
||||
SECTION("erasing every element through the returned iterator")
|
||||
{
|
||||
void_erase_json j;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
j["k" + std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
std::size_t erased = 0;
|
||||
for (auto it = j.begin(); it != j.end(); ++erased)
|
||||
{
|
||||
it = j.erase(it);
|
||||
}
|
||||
CHECK(erased == 8);
|
||||
CHECK(j.empty());
|
||||
}
|
||||
|
||||
SECTION("erasing in the middle returns the following element")
|
||||
{
|
||||
void_erase_json j;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
j["k" + std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
auto it = j.begin();
|
||||
++it;
|
||||
const auto after = j.erase(it);
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(after.key() == "k2");
|
||||
CHECK(after.value() == 2);
|
||||
CHECK(!j.contains("k1"));
|
||||
}
|
||||
|
||||
SECTION("the other erase overloads are unaffected")
|
||||
{
|
||||
void_erase_json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = 2;
|
||||
j["c"] = 3;
|
||||
|
||||
CHECK(j.erase("a") == 1);
|
||||
CHECK(j.erase("nope") == 0);
|
||||
j.erase(j.begin(), j.end());
|
||||
CHECK(j.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("object type without key_compare")
|
||||
{
|
||||
SECTION("object_comparator_t falls back to default_object_comparator_t")
|
||||
|
||||
Reference in New Issue
Block a user