Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f674a61318 | ||
|
|
106065e62d | ||
|
|
ebab8a11cc | ||
|
|
f87b707292 |
No files matched your search
@@ -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,6 +9,15 @@ published on the [GitHub releases page](https://github.com/nlohmann/json/release
|
|||||||
This documentation is built from the `develop` branch and may describe changes that are not part of a release
|
This documentation is built from the `develop` branch and may describe changes that are not part of a release
|
||||||
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge.
|
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- Converting an rvalue container to JSON (`json j = std::move(vec);`, `json j = std::map<std::string, std::string>{...};`)
|
||||||
|
now **moves** the elements instead of copying them (e.g., `std::vector<std::string>` with 100,000 strings: 4.7 ms to
|
||||||
|
2.3 ms). Two consequences: the source container is left in a valid but unspecified (moved-from) state, and if an
|
||||||
|
exception is thrown during the conversion, the source may be partially moved-from, so the strong exception guarantee
|
||||||
|
no longer holds for the source of an rvalue conversion (the JSON value itself is unaffected). Lvalues and `const`
|
||||||
|
objects are still copied. See [`basic_json`](../api/basic_json/basic_json.md).
|
||||||
|
|
||||||
## v3.12.0 (2025-04-11)
|
## v3.12.0 (2025-04-11)
|
||||||
|
|
||||||
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
|
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
|
||||||
|
|||||||
@@ -167,6 +167,17 @@ struct external_constructor<value_t::array>
|
|||||||
j.replace_value(value_t::array, value);
|
j.replace_value(value_t::array, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// an rvalue container: move its elements
|
||||||
|
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||||
|
static void construct_moving(BasicJsonType& j, CompatibleArrayType& arr)
|
||||||
|
{
|
||||||
|
using std::begin;
|
||||||
|
using std::end;
|
||||||
|
|
||||||
|
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
||||||
|
j.replace_value(value_t::array, value);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
||||||
{
|
{
|
||||||
@@ -217,6 +228,17 @@ struct external_constructor<value_t::object>
|
|||||||
j.replace_value(value_t::object, value);
|
j.replace_value(value_t::object, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// an rvalue container: move its values
|
||||||
|
template<typename BasicJsonType, typename CompatibleObjectType>
|
||||||
|
static void construct_moving(BasicJsonType& j, CompatibleObjectType& obj)
|
||||||
|
{
|
||||||
|
using std::begin;
|
||||||
|
using std::end;
|
||||||
|
|
||||||
|
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
||||||
|
j.replace_value(value_t::object, value);
|
||||||
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename CompatibleObjectType,
|
template < typename BasicJsonType, typename CompatibleObjectType,
|
||||||
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
|
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
|
||||||
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
|
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
|
||||||
@@ -354,6 +376,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)
|
||||||
{
|
{
|
||||||
|
|||||||
+325
-94
@@ -585,26 +585,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
|
||||||
object = create<object_t>();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
|
||||||
array = create<array_t>();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
|
||||||
string = create<string_t>("");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
{
|
||||||
binary = create<binary_t>();
|
// allocating: kept out of line, so the scalar cases below inline
|
||||||
|
init_owned(t);
|
||||||
break;
|
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
|
/// constructor for strings
|
||||||
json_value(const string_t& value) : string(create<string_t>(value)) {}
|
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)
|
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)
|
switch (t)
|
||||||
{
|
{
|
||||||
@@ -1079,51 +1104,64 @@ public:
|
|||||||
return next;
|
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
|
/// Only JSON_DIAGNOSTICS uses the value, to detect a reallocation that
|
||||||
/// would invalidate the parent pointers. Array types that do not have a
|
/// would invalidate the parent pointers. Containers that do not have a
|
||||||
/// capacity() member function report unknown_size(), which is treated as
|
/// capacity() member function report unknown_size() if their elements may
|
||||||
/// "the elements may have moved".
|
/// move (arrays, ordered_map) and 0 if they never do (std::map).
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
template < typename A = array_t, detail::enable_if_t < detail::has_capacity<A>::value, int > = 0 >
|
template < typename C, detail::enable_if_t < detail::has_capacity<C>::value, int > = 0 >
|
||||||
std::size_t array_capacity() const noexcept
|
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 >
|
template < typename C, detail::enable_if_t < !detail::has_capacity<C>::value, int > = 0 >
|
||||||
std::size_t array_capacity() const noexcept
|
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
|
#else
|
||||||
static constexpr std::size_t array_capacity() noexcept
|
static constexpr std::size_t container_capacity() noexcept
|
||||||
{
|
{
|
||||||
return detail::unknown_size();
|
return detail::unknown_size();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// @brief set the parent of a value that has just been added to an array
|
/// @brief set the parent of a value that has just been added to an array or object
|
||||||
/// @param j the added value
|
/// @param j the added (or looked-up) value
|
||||||
/// @param old_capacity the value @ref array_capacity() returned before the
|
/// @param old_capacity the value @ref container_capacity() returned before the
|
||||||
/// insertion
|
/// 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
|
#if JSON_DIAGNOSTICS
|
||||||
// see https://github.com/nlohmann/json/issues/2838
|
// see https://github.com/nlohmann/json/issues/2838
|
||||||
JSON_ASSERT(type() == value_t::array);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
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 capacity has changed, or the container type does not let us
|
||||||
// the elements may have moved, so update all parents
|
// tell: the elements may have moved, so update all parents
|
||||||
set_parents();
|
set_parents();
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static_cast<void>(old_capacity);
|
static_cast<void>(old_capacity);
|
||||||
#endif
|
#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)
|
reference set_parent(reference j)
|
||||||
@@ -2546,18 +2584,17 @@ public:
|
|||||||
// check of passed value is valid
|
// check of passed value is valid
|
||||||
other.assert_invariant();
|
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
|
// null, boolean, numbers, discarded: plain copy of the union
|
||||||
// for every element, once per nesting level
|
m_data.m_value = other.m_data.m_value;
|
||||||
copy_structured(other);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
copy_leaf_value(other, *this);
|
copy_owning(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_parents();
|
|
||||||
assert_invariant();
|
assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2787,6 +2824,22 @@ public:
|
|||||||
// value access //
|
// 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)
|
/// get a pointer to the value (object)
|
||||||
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
||||||
{
|
{
|
||||||
@@ -3445,7 +3498,7 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||||
reference at(size_type idx)
|
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
|
/// @brief access specified array element with bounds checking
|
||||||
@@ -3459,7 +3512,7 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||||
reference at(const typename object_t::key_type& key)
|
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
|
/// @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>
|
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||||
reference at(KeyType && key)
|
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
|
/// @brief access specified object element with bounds checking
|
||||||
@@ -3513,13 +3566,13 @@ public:
|
|||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
// remember array size & capacity before resizing
|
// remember array size & capacity before resizing
|
||||||
const auto old_size = m_data.m_value.array->size();
|
const auto old_size = m_data.m_value.array->size();
|
||||||
const auto old_capacity = array_capacity();
|
const auto old_capacity = container_capacity();
|
||||||
#endif
|
#endif
|
||||||
m_data.m_value.array->resize(idx + 1);
|
m_data.m_value.array->resize(idx + 1);
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
||||||
|| array_capacity() != old_capacity))
|
|| container_capacity() != old_capacity))
|
||||||
{
|
{
|
||||||
// capacity has changed: update all parents
|
// capacity has changed: update all parents
|
||||||
set_parents();
|
set_parents();
|
||||||
@@ -3566,8 +3619,9 @@ public:
|
|||||||
// operator[] only works for objects
|
// operator[] only works for objects
|
||||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||||
{
|
{
|
||||||
|
const auto old_capacity = container_capacity();
|
||||||
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
|
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");
|
throw_subscript_wrong_type("string");
|
||||||
@@ -3617,8 +3671,9 @@ public:
|
|||||||
// operator[] only works for objects
|
// operator[] only works for objects
|
||||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
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);
|
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");
|
throw_subscript_wrong_type("string");
|
||||||
@@ -4524,9 +4579,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the element to the array (move semantics)
|
// 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));
|
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
|
// 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
|
// 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);
|
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
|
/// @brief add an object to an array
|
||||||
@@ -4585,8 +4640,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the element to the object
|
// add the element to the object
|
||||||
|
const auto old_capacity = container_capacity();
|
||||||
auto res = m_data.m_value.object->insert(val);
|
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
|
/// @brief add an object to an object
|
||||||
@@ -4639,9 +4695,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the element to the array (perfect forwarding)
|
// 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)...);
|
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
|
/// @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)
|
// 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)...);
|
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
|
// create a result iterator and set iterator to the result of emplace
|
||||||
auto it = begin();
|
auto it = begin();
|
||||||
@@ -4962,9 +5019,9 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set_parent() also repairs the other members, which ordered_json
|
// operator[] repairs the other members if ordered_json relocates
|
||||||
// relocates when adding a key makes its vector grow
|
// them; assigning through it keeps this O(1) per key
|
||||||
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
|
operator[](it.key()) = std::move(it.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5014,9 +5071,8 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set_parent() also repairs the other members, which ordered_json
|
// operator[] repairs the other members if ordered_json relocates them
|
||||||
// relocates when adding a key makes its vector grow
|
target->operator[](first.key()) = std::move(first.value());
|
||||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
|
|
||||||
++first;
|
++first;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5158,6 +5214,20 @@ public:
|
|||||||
/// @name lexicographical comparison operators
|
/// @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
|
// note parentheses around operands are necessary; see
|
||||||
// https://github.com/nlohmann/json/issues/1530
|
// https://github.com/nlohmann/json/issues/1530
|
||||||
// Mixed signed/unsigned integer comparisons check whether the signed value
|
// 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); \
|
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
case value_t::null: \
|
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
||||||
return (null_result); \
|
\
|
||||||
\
|
|
||||||
case value_t::string: \
|
case value_t::string: \
|
||||||
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.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: \
|
case value_t::binary: \
|
||||||
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
|
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
|
||||||
\
|
\
|
||||||
@@ -5255,6 +5312,24 @@ public:
|
|||||||
\
|
\
|
||||||
return (default_result);
|
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:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
// returns true if:
|
// returns true if:
|
||||||
// - any operand is NaN and the other operand is of number type
|
// - any operand is NaN and the other operand is of number type
|
||||||
@@ -5282,6 +5357,168 @@ public:
|
|||||||
return compares_unordered(*this, rhs, inverse);
|
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:
|
public:
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
/// @brief comparison: equal
|
/// @brief comparison: equal
|
||||||
@@ -5293,11 +5530,11 @@ public:
|
|||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
const_reference lhs = *this;
|
const_reference lhs = *this;
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
|
||||||
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: equal
|
/// @brief comparison: equal
|
||||||
@@ -5314,13 +5551,8 @@ public:
|
|||||||
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
|
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
|
||||||
{
|
{
|
||||||
const_reference lhs = *this;
|
const_reference lhs = *this;
|
||||||
// default_result is used if we cannot compare values. In that case,
|
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD*
|
||||||
// we compare types.
|
return three_way_general(lhs, rhs);
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: 3-way
|
/// @brief comparison: 3-way
|
||||||
@@ -5408,11 +5640,11 @@ public:
|
|||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
|
||||||
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The friend comparisons with a scalar name the JSON type via decltype of
|
// 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/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||||
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
|
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
|
||||||
{
|
{
|
||||||
// default_result is used if we cannot compare values. In that case,
|
JSON_IMPLEMENT_SCALAR_FAST( <, false)
|
||||||
// we compare types. Note we have to call the operator explicitly,
|
return less_general(lhs, rhs);
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: less than
|
/// @brief comparison: less than
|
||||||
@@ -5582,6 +5811,8 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#undef JSON_IMPLEMENT_OPERATOR
|
#undef JSON_IMPLEMENT_OPERATOR
|
||||||
|
#undef JSON_IMPLEMENT_SCALAR_FAST
|
||||||
|
#undef JSON_IMPLEMENT_SCALAR_CASES
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
|
|||||||
Generated
+421
-94
@@ -7140,6 +7140,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)
|
||||||
{
|
{
|
||||||
@@ -7190,6 +7201,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)
|
||||||
@@ -7327,6 +7349,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
|
||||||
@@ -7400,6 +7482,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)
|
||||||
{
|
{
|
||||||
@@ -30229,26 +30325,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
|
||||||
object = create<object_t>();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
|
||||||
array = create<array_t>();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
|
||||||
string = create<string_t>("");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
{
|
||||||
binary = create<binary_t>();
|
// allocating: kept out of line, so the scalar cases below inline
|
||||||
|
init_owned(t);
|
||||||
break;
|
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
|
/// constructor for strings
|
||||||
json_value(const string_t& value) : string(create<string_t>(value)) {}
|
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)
|
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)
|
switch (t)
|
||||||
{
|
{
|
||||||
@@ -30723,51 +30844,64 @@ public:
|
|||||||
return next;
|
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
|
/// Only JSON_DIAGNOSTICS uses the value, to detect a reallocation that
|
||||||
/// would invalidate the parent pointers. Array types that do not have a
|
/// would invalidate the parent pointers. Containers that do not have a
|
||||||
/// capacity() member function report unknown_size(), which is treated as
|
/// capacity() member function report unknown_size() if their elements may
|
||||||
/// "the elements may have moved".
|
/// move (arrays, ordered_map) and 0 if they never do (std::map).
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
template < typename A = array_t, detail::enable_if_t < detail::has_capacity<A>::value, int > = 0 >
|
template < typename C, detail::enable_if_t < detail::has_capacity<C>::value, int > = 0 >
|
||||||
std::size_t array_capacity() const noexcept
|
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 >
|
template < typename C, detail::enable_if_t < !detail::has_capacity<C>::value, int > = 0 >
|
||||||
std::size_t array_capacity() const noexcept
|
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
|
#else
|
||||||
static constexpr std::size_t array_capacity() noexcept
|
static constexpr std::size_t container_capacity() noexcept
|
||||||
{
|
{
|
||||||
return detail::unknown_size();
|
return detail::unknown_size();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// @brief set the parent of a value that has just been added to an array
|
/// @brief set the parent of a value that has just been added to an array or object
|
||||||
/// @param j the added value
|
/// @param j the added (or looked-up) value
|
||||||
/// @param old_capacity the value @ref array_capacity() returned before the
|
/// @param old_capacity the value @ref container_capacity() returned before the
|
||||||
/// insertion
|
/// 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
|
#if JSON_DIAGNOSTICS
|
||||||
// see https://github.com/nlohmann/json/issues/2838
|
// see https://github.com/nlohmann/json/issues/2838
|
||||||
JSON_ASSERT(type() == value_t::array);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
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 capacity has changed, or the container type does not let us
|
||||||
// the elements may have moved, so update all parents
|
// tell: the elements may have moved, so update all parents
|
||||||
set_parents();
|
set_parents();
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static_cast<void>(old_capacity);
|
static_cast<void>(old_capacity);
|
||||||
#endif
|
#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)
|
reference set_parent(reference j)
|
||||||
@@ -32190,18 +32324,17 @@ public:
|
|||||||
// check of passed value is valid
|
// check of passed value is valid
|
||||||
other.assert_invariant();
|
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
|
// null, boolean, numbers, discarded: plain copy of the union
|
||||||
// for every element, once per nesting level
|
m_data.m_value = other.m_data.m_value;
|
||||||
copy_structured(other);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
copy_leaf_value(other, *this);
|
copy_owning(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_parents();
|
|
||||||
assert_invariant();
|
assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32431,6 +32564,22 @@ public:
|
|||||||
// value access //
|
// 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)
|
/// get a pointer to the value (object)
|
||||||
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
||||||
{
|
{
|
||||||
@@ -33089,7 +33238,7 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||||
reference at(size_type idx)
|
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
|
/// @brief access specified array element with bounds checking
|
||||||
@@ -33103,7 +33252,7 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||||
reference at(const typename object_t::key_type& key)
|
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
|
/// @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>
|
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||||
reference at(KeyType && key)
|
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
|
/// @brief access specified object element with bounds checking
|
||||||
@@ -33157,13 +33306,13 @@ public:
|
|||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
// remember array size & capacity before resizing
|
// remember array size & capacity before resizing
|
||||||
const auto old_size = m_data.m_value.array->size();
|
const auto old_size = m_data.m_value.array->size();
|
||||||
const auto old_capacity = array_capacity();
|
const auto old_capacity = container_capacity();
|
||||||
#endif
|
#endif
|
||||||
m_data.m_value.array->resize(idx + 1);
|
m_data.m_value.array->resize(idx + 1);
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
||||||
|| array_capacity() != old_capacity))
|
|| container_capacity() != old_capacity))
|
||||||
{
|
{
|
||||||
// capacity has changed: update all parents
|
// capacity has changed: update all parents
|
||||||
set_parents();
|
set_parents();
|
||||||
@@ -33210,8 +33359,9 @@ public:
|
|||||||
// operator[] only works for objects
|
// operator[] only works for objects
|
||||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||||
{
|
{
|
||||||
|
const auto old_capacity = container_capacity();
|
||||||
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
|
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");
|
throw_subscript_wrong_type("string");
|
||||||
@@ -33261,8 +33411,9 @@ public:
|
|||||||
// operator[] only works for objects
|
// operator[] only works for objects
|
||||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
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);
|
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");
|
throw_subscript_wrong_type("string");
|
||||||
@@ -34168,9 +34319,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the element to the array (move semantics)
|
// 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));
|
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
|
// 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
|
// 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);
|
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
|
/// @brief add an object to an array
|
||||||
@@ -34229,8 +34380,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the element to the object
|
// add the element to the object
|
||||||
|
const auto old_capacity = container_capacity();
|
||||||
auto res = m_data.m_value.object->insert(val);
|
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
|
/// @brief add an object to an object
|
||||||
@@ -34283,9 +34435,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the element to the array (perfect forwarding)
|
// 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)...);
|
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
|
/// @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)
|
// 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)...);
|
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
|
// create a result iterator and set iterator to the result of emplace
|
||||||
auto it = begin();
|
auto it = begin();
|
||||||
@@ -34606,9 +34759,9 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set_parent() also repairs the other members, which ordered_json
|
// operator[] repairs the other members if ordered_json relocates
|
||||||
// relocates when adding a key makes its vector grow
|
// them; assigning through it keeps this O(1) per key
|
||||||
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
|
operator[](it.key()) = std::move(it.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34658,9 +34811,8 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set_parent() also repairs the other members, which ordered_json
|
// operator[] repairs the other members if ordered_json relocates them
|
||||||
// relocates when adding a key makes its vector grow
|
target->operator[](first.key()) = std::move(first.value());
|
||||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
|
|
||||||
++first;
|
++first;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34802,6 +34954,20 @@ public:
|
|||||||
/// @name lexicographical comparison operators
|
/// @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
|
// note parentheses around operands are necessary; see
|
||||||
// https://github.com/nlohmann/json/issues/1530
|
// https://github.com/nlohmann/json/issues/1530
|
||||||
// Mixed signed/unsigned integer comparisons check whether the signed value
|
// 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); \
|
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
case value_t::null: \
|
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
||||||
return (null_result); \
|
\
|
||||||
\
|
|
||||||
case value_t::string: \
|
case value_t::string: \
|
||||||
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.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: \
|
case value_t::binary: \
|
||||||
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
|
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
|
||||||
\
|
\
|
||||||
@@ -34899,6 +35052,24 @@ public:
|
|||||||
\
|
\
|
||||||
return (default_result);
|
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:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
// returns true if:
|
// returns true if:
|
||||||
// - any operand is NaN and the other operand is of number type
|
// - any operand is NaN and the other operand is of number type
|
||||||
@@ -34926,6 +35097,168 @@ public:
|
|||||||
return compares_unordered(*this, rhs, inverse);
|
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:
|
public:
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
/// @brief comparison: equal
|
/// @brief comparison: equal
|
||||||
@@ -34937,11 +35270,11 @@ public:
|
|||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
const_reference lhs = *this;
|
const_reference lhs = *this;
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
|
||||||
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: equal
|
/// @brief comparison: equal
|
||||||
@@ -34958,13 +35291,8 @@ public:
|
|||||||
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
|
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
|
||||||
{
|
{
|
||||||
const_reference lhs = *this;
|
const_reference lhs = *this;
|
||||||
// default_result is used if we cannot compare values. In that case,
|
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD*
|
||||||
// we compare types.
|
return three_way_general(lhs, rhs);
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: 3-way
|
/// @brief comparison: 3-way
|
||||||
@@ -35052,11 +35380,11 @@ public:
|
|||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
|
||||||
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
return equal_dispatch(lhs, rhs, equal_bounded_containers {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The friend comparisons with a scalar name the JSON type via decltype of
|
// 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/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||||
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
|
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
|
||||||
{
|
{
|
||||||
// default_result is used if we cannot compare values. In that case,
|
JSON_IMPLEMENT_SCALAR_FAST( <, false)
|
||||||
// we compare types. Note we have to call the operator explicitly,
|
return less_general(lhs, rhs);
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: less than
|
/// @brief comparison: less than
|
||||||
@@ -35226,6 +35551,8 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#undef JSON_IMPLEMENT_OPERATOR
|
#undef JSON_IMPLEMENT_OPERATOR
|
||||||
|
#undef JSON_IMPLEMENT_SCALAR_FAST
|
||||||
|
#undef JSON_IMPLEMENT_SCALAR_CASES
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user