Compare commits

..
Author SHA1 Message Date
Niels Lohmann 15b0ff3be6 Merge branch 'develop' into guard-page-tests
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 22:56:08 +02:00
Niels Lohmann e4cb826600 Make the guard page test work without exceptions and under UBSan
Under JSON_NOEXCEPTION, the error paths abort instead of throwing, so the
outcomes are now computed with allow_exceptions=false there. Copying the
input with std::copy from char to unsigned char tripped Clang's implicit
conversion sanitizer; std::memcpy avoids the conversion.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 22:55:39 +02:00
Niels Lohmann 9dc60905ed Rename the reader parameter to avoid a flawfinder false positive
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:37:45 +02:00
Niels Lohmann 467670aa7a Test that the contiguous fast paths never read outside the input
The bulk string scanner, the SWAR searches, the UTF-8 prefix validator,
the 8-digit number loop, the 8-byte indentation skip, and the BSON/BON8
bulk string readers load several bytes at a time from contiguous input.
ASan misses over-reads into the slack of the same heap allocation, and
fuzzers rarely end an input exactly at a page boundary.

The new test copies each input directly in front of, and directly
behind, an inaccessible page (mmap/mprotect on POSIX,
VirtualAlloc/VirtualProtect on Windows), so reading even one byte
outside the input crashes. It covers the string-scan functions directly
and json::parse, accept, sax_parse (with and without comments) and all
binary readers through the public API, with inputs ending at every byte
of strings, escapes, UTF-8 sequences, numbers, literals, indentation
runs, and binary documents. Each result must match parsing the same
input from a std::string.

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

No files matched your search

+1 -11
View File
@@ -68,14 +68,6 @@ 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.
@@ -244,9 +236,7 @@ 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. **Exception:** when `val` is an rvalue array-like or object-like container, its elements are moved one JSON value.
by one; if an exception is thrown in the middle of the conversion (e.g., an allocation failure), the JSON value is
unchanged, but the source container may be left partially moved-from.
4. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no 4. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any `to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
JSON value. JSON value.
-9
View File
@@ -9,15 +9,6 @@ published on the [GitHub releases page](https://github.com/nlohmann/json/release
This documentation is built from the `develop` branch and may describe changes that are not part of a release This documentation is built from the `develop` branch and may describe changes that are not part of a release
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge. yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge.
## Unreleased
- Converting an rvalue container to JSON (`json j = std::move(vec);`, `json j = std::map<std::string, std::string>{...};`)
now **moves** the elements instead of copying them (e.g., `std::vector<std::string>` with 100,000 strings: 4.7 ms to
2.3 ms). Two consequences: the source container is left in a valid but unspecified (moved-from) state, and if an
exception is thrown during the conversion, the source may be partially moved-from, so the strong exception guarantee
no longer holds for the source of an rvalue conversion (the JSON value itself is unaffected). Lvalues and `const`
objects are still copied. See [`basic_json`](../api/basic_json/basic_json.md).
## v3.12.0 (2025-04-11) ## v3.12.0 (2025-04-11)
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible. Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
@@ -266,22 +266,6 @@ template<typename ConstructibleArrayType>
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{} {}
// append through push_back() where available; containers without it (such as
// std::set) fall back to insert(end(), ...), which is what std::inserter did
template<typename ConstructibleArrayType, typename ValueType>
auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/)
-> decltype(arr.push_back(std::forward<ValueType>(value)), void())
{
arr.push_back(std::forward<ValueType>(value));
}
template<typename ConstructibleArrayType, typename ValueType>
void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/)
{
using std::end;
arr.insert(end(arr), std::forward<ValueType>(value));
}
template<typename BasicJsonType, typename ConstructibleArrayType, template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t< enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value, std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
@@ -291,15 +275,17 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
j.template get<typename ConstructibleArrayType::value_type>(), j.template get<typename ConstructibleArrayType::value_type>(),
void()) void())
{ {
using std::end;
ConstructibleArrayType ret; ConstructibleArrayType ret;
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>(); from_json_array_reserve(ret, j.size(), priority_tag<1> {});
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {}); std::transform(j.begin(), j.end(),
for (const auto& elem : *inner_array) std::inserter(ret, end(ret)), [](const BasicJsonType & i)
{ {
// get<BasicJsonType>() returns *this, this won't call a from_json // get<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType // method when value_type is BasicJsonType
from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {}); return i.template get<typename ConstructibleArrayType::value_type>();
} });
arr = std::move(ret); arr = std::move(ret);
} }
@@ -397,13 +383,9 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret; ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {}); from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
using value_type = typename ConstructibleObjectType::value_type;
for (const auto& p : *inner_object) for (const auto& p : *inner_object)
{ {
// the keys of the source are sorted for the usual object types, so ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
// hinting the end makes every insertion constant-time (for containers
// that order their keys the same way)
ret.insert(ret.end(), value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()));
} }
obj = std::move(ret); obj = std::move(ret);
} }
@@ -167,17 +167,6 @@ 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)
{ {
@@ -228,17 +217,6 @@ 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)
@@ -376,66 +354,6 @@ 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
@@ -509,20 +427,6 @@ 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)
{ {
+3 -6
View File
@@ -674,19 +674,16 @@ class json_sax_dom_callback_parser
if (keep && ref_stack.back()) if (keep && ref_stack.back())
{ {
auto& obj = *ref_stack.back()->m_data.m_value.object; auto& obj = *ref_stack.back()->m_data.m_value.object;
// a single lookup: insert the placeholder, or find the existing member const auto it = obj.find(val);
const auto inserted = obj.emplace(val, discarded); if (it != obj.end())
const auto it = inserted.first;
if (!inserted.second)
{ {
// this is a duplicate key (legal in JSON); remember its // this is a duplicate key (legal in JSON); remember its
// current value so it can be restored later if the new // current value so it can be restored later if the new
// value is rejected by the callback, instead of being // value is rejected by the callback, instead of being
// erased together with the discarded placeholder // erased together with the discarded placeholder
duplicate_key_stash.emplace_back(&(it->second), it->second); duplicate_key_stash.emplace_back(&(it->second), it->second);
it->second = discarded;
} }
object_element = &(it->second); object_element = &(obj[val] = discarded);
} }
return true; return true;
+90 -346
View File
@@ -16,6 +16,8 @@
#include <iosfwd> // ostream #include <iosfwd> // ostream
#endif // JSON_NO_IO #endif // JSON_NO_IO
#include <limits> // max #include <limits> // max
#include <map> // map
#include <numeric> // accumulate
#include <string> // string #include <string> // string
#include <utility> // move #include <utility> // move
#include <vector> // vector #include <vector> // vector
@@ -67,21 +69,12 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/ /// @sa https://json.nlohmann.me/api/json_pointer/to_string/
string_t to_string() const string_t to_string() const
{ {
// one buffer: every token adds its escaped form (at least its length) and a slash return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
std::size_t length = 0; string_t{},
for (const auto& reference_token : reference_tokens) [](const string_t& a, const string_t& b)
{ {
length += reference_token.size() + 1; return detail::concat<string_t>(a, '/', detail::escape(b));
} });
string_t result;
result.reserve(length);
for (const auto& reference_token : reference_tokens)
{
result.append("/", 1);
detail::escape_into(result, reference_token);
}
return result;
} }
/// @brief return a string representation of the JSON pointer /// @brief return a string representation of the JSON pointer
@@ -136,29 +129,21 @@ class json_pointer
friend json_pointer operator/(const json_pointer& lhs, friend json_pointer operator/(const json_pointer& lhs,
const json_pointer& rhs) const json_pointer& rhs)
{ {
json_pointer result; return json_pointer(lhs) /= rhs;
result.reference_tokens.reserve(lhs.reference_tokens.size() + rhs.reference_tokens.size());
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
return result;
} }
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param) friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
{ {
json_pointer result; return json_pointer(lhs) /= std::move(token);
result.reference_tokens.reserve(lhs.reference_tokens.size() + 1);
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
result.reference_tokens.push_back(std::move(token));
return result;
} }
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx) friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
{ {
return lhs / detail::to_string<string_t>(array_idx); return json_pointer(lhs) /= array_idx;
} }
/// @brief returns the parent of this JSON pointer /// @brief returns the parent of this JSON pointer
@@ -170,8 +155,8 @@ class json_pointer
return *this; return *this;
} }
json_pointer res; json_pointer res = *this;
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1); res.pop_back();
return res; return res;
} }
@@ -260,14 +245,14 @@ class json_pointer
private: private:
/// @throw out_of_range.405 always /// @throw out_of_range.405 always
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_no_parent() JSON_HEDLEY_NO_RETURN static void throw_no_parent()
{ {
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
} }
/// @throw out_of_range.404 always /// @throw out_of_range.404 always
template<typename BasicJsonContext> template<typename BasicJsonContext>
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
{ {
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(context); static_cast<void>(context);
@@ -320,28 +305,6 @@ class json_pointer
return array_index_status::not_a_number; return array_index_status::not_a_number;
} }
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
if (!s.empty() && s.size() <= max_fast_digits)
{
size_type acc = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const char c = s[i];
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
{
break; // not a plain number: let strtoull decide
}
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
{
idx = acc;
return array_index_status::ok;
}
}
}
const char* p = s.data(); const char* p = s.data();
char* p_end = nullptr; // NOLINT(misc-const-correctness) char* p_end = nullptr; // NOLINT(misc-const-correctness)
errno = 0; // strtoull doesn't reset errno errno = 0; // strtoull doesn't reset errno
@@ -398,83 +361,6 @@ class json_pointer
return idx; return idx;
} }
/*!
@brief compare with a JSON pointer string without splitting it into tokens
@throw parse_error.107 if the string is not empty or begins with '/'
@throw parse_error.108 if character '~' is not followed by '0' or '1'
*/
bool equals_string(const string_t& reference_string) const
{
if (reference_string.empty())
{
return reference_tokens.empty();
}
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
}
// the whole string is validated, whether or not it matches
const std::size_t size = reference_string.size();
for (std::size_t pos = reference_string.find_first_of('~');
pos != string_t::npos;
pos = reference_string.find_first_of('~', pos + 1))
{
if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
}
}
// compare token by token, unescaping the string on the fly
std::size_t pos = 0; // the slash in front of the next token
for (const auto& reference_token : reference_tokens)
{
if (pos >= size)
{
return false;
}
std::size_t i = pos + 1;
std::size_t j = 0;
const std::size_t token_size = reference_token.size();
while (i < size && reference_string[i] != '/')
{
char c = reference_string[i++];
if (c == '~')
{
c = (reference_string[i++] == '0') ? '~' : '/';
}
if (j == token_size || reference_token[j] != c)
{
return false;
}
++j;
}
if (j != token_size)
{
return false;
}
pos = i;
}
return pos == size;
}
/// @brief object member access with a key that is only copied if it is inserted
template<typename BasicJsonType>
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
auto& members = *object.m_data.m_value.object;
const auto it = members.find(key);
if (it != members.end())
{
return it->second;
}
return object.operator[](key);
}
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
json_pointer top() const json_pointer top() const
{ {
@@ -498,141 +384,84 @@ class json_pointer
the result independent of the order in which the flattened object is the result independent of the order in which the flattened object is
iterated, which is unspecified for some object types. iterated, which is unspecified for some object types.
The prefixes form a tree and are numbered (number 0 is the empty prefix), The prefixes form a tree and are numbered, so each of them is stored only
so each of them is stored only once (as a node) rather than as a copy of once (as a node) rather than as a copy of all of its reference tokens.
all of its reference tokens. The tree does not own the reference tokens:
it refers to the tokens of the parsed keys, which must outlive it.
*/ */
struct prefix_tree struct prefix_tree
{ {
// open-addressing hash table from (parent, last reference token) to // children[id] maps a reference token to the number of the prefix
// the number of the node; empty slots have token == nullptr // extended by that token; number 0 is the empty prefix
struct slot std::vector<std::map<string_t, std::size_t>> children;
{
std::size_t hash;
std::size_t parent;
const string_t* token;
std::size_t id;
slot() noexcept
: hash(0), parent(0), token(nullptr), id(0)
{}
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept
: hash(h), parent(p), token(t), id(i)
{}
};
std::vector<slot> slots;
std::size_t used = 0;
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
{
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
for (std::size_t i = 0; i < token.size(); ++i)
{
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
}
return h ^ (h >> 29u);
}
void grow()
{
std::vector<slot> old(slots.size() * 2);
old.swap(slots);
const std::size_t mask = slots.size() - 1;
for (const auto& s : old)
{
if (s.token != nullptr)
{
std::size_t i = s.hash & mask;
while (slots[i].token != nullptr)
{
i = (i + 1) & mask;
}
slots[i] = s;
}
}
}
// parent[id] is the number of the prefix without its last token
std::vector<std::size_t> parent;
// is_array[id] is true iff some flattened key has the reference token // is_array[id] is true iff some flattened key has the reference token
// 0 directly below the prefix with number id // 0 directly below the prefix with number id
std::vector<bool> is_array; std::vector<bool> is_array;
// start with the empty prefix only // start with the empty prefix only
prefix_tree() prefix_tree()
: slots(16) : children(1)
, parent(1, 0)
, is_array(1, false) , is_array(1, false)
{} {}
// return the number of the prefix with number id extended by // return the number of the prefix with number id extended by
// reference_token, adding it if it is new // reference_token, adding it if it is new
std::size_t add_child(std::size_t id, const string_t& reference_token) std::size_t add_child(std::size_t id, string_t&& reference_token)
{ {
const std::size_t hash = hash_of(id, reference_token);
const std::size_t mask = slots.size() - 1;
std::size_t i = hash & mask;
while (slots[i].token != nullptr)
{
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
{
return slots[i].id;
}
i = (i + 1) & mask;
}
// new prefix
const std::size_t child = parent.size();
slots[i] = slot(hash, id, &reference_token, child);
if (reference_token == "0") if (reference_token == "0")
{ {
is_array[id] = true; is_array[id] = true;
} }
parent.push_back(id);
is_array.push_back(false);
// keep the load factor below 1/2 // read the number before the emplace_back below, which may
if (++used * 2 > slots.size()) // reallocate children and invalidate the iterator
const std::size_t next = children.size();
const auto inserted = children[id].emplace(std::move(reference_token), next);
const std::size_t child = inserted.first->second;
if (inserted.second)
{ {
grow(); children.emplace_back();
is_array.push_back(false);
} }
return child; return child;
} }
// return the number of the prefix with number id extended by
// reference_token, which must have been added before
std::size_t find_child(std::size_t id, const string_t& reference_token) const
{
const auto it = children[id].find(reference_token);
JSON_ASSERT(it != children[id].end());
return it->second;
}
}; };
/*! /*!
@brief create and return a reference to the pointed to value @brief create and return a reference to the pointed to value
@param[in] j the value to create the pointed to value in Complexity: Linear in the number of reference tokens (times the logarithm
@param[in] tokens the reference tokens of the pointer of the number of siblings for the prefix lookup).
@param[in] prefixes prefixes[i] is the number in @a tree of the prefix
consisting of the first i reference tokens
Complexity: Linear in the number of reference tokens.
@throw parse_error.106 if an array index begins with '0' @throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if array index is not a number @throw parse_error.109 if array index is not a number
@throw type_error.313 if value cannot be unflattened @throw type_error.313 if value cannot be unflattened
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens, BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
{ {
auto* result = &j; auto* result = &j;
// the number of the prefix consumed so far; used to look up whether
// the value to be created below is an array or an object
std::size_t id = 0;
// in case no reference tokens exist, return a reference to the JSON value // in case no reference tokens exist, return a reference to the JSON value
// j which will be overwritten by a primitive value // j which will be overwritten by a primitive value
for (std::size_t i = 0; i < tokens.size(); ++i) for (const auto& reference_token : reference_tokens)
{ {
const auto& reference_token = tokens[i];
switch (result->type()) switch (result->type())
{ {
case detail::value_t::null: case detail::value_t::null:
{ {
if (tree.is_array[prefixes[i]]) if (tree.is_array[id])
{ {
// some reference token below this position is 0, so the // some reference token below this position is 0, so the
// value is an array // value is an array
@@ -649,7 +478,7 @@ class json_pointer
case detail::value_t::object: case detail::value_t::object:
{ {
// create an entry in the object // create an entry in the object
result = &object_member(*result, reference_token); result = &result->operator[](reference_token);
break; break;
} }
@@ -676,6 +505,8 @@ class json_pointer
default: default:
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
} }
id = tree.find_child(id, reference_token);
} }
return *result; return *result;
@@ -727,7 +558,7 @@ class json_pointer
case detail::value_t::object: case detail::value_t::object:
{ {
// use unchecked object access // use unchecked object access
ptr = &object_member(*ptr, reference_token); ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -777,13 +608,8 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
const auto it = ptr->m_data.m_value.object->find(reference_token); // note: at performs range check
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) ptr = &ptr->at(reference_token);
{
// at() throws out_of_range.403 with the usual message
ptr->at(reference_token);
}
ptr = &it->second;
break; break;
} }
@@ -804,7 +630,7 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat( JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
"array index ", std::to_string(idx), " is out of range"), ptr)); "array index ", std::to_string(idx), " is out of range"), ptr));
} }
ptr = &(*ptr->m_data.m_value.array)[idx]; ptr = &ptr->operator[](idx);
break; break;
} }
@@ -852,9 +678,7 @@ class json_pointer
{ {
// use unchecked object access; the const operator[] // use unchecked object access; the const operator[]
// asserts that the key exists // asserts that the key exists
const auto it = ptr->m_data.m_value.object->find(reference_token); ptr = &ptr->operator[](reference_token);
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
ptr = &it->second;
break; break;
} }
@@ -868,9 +692,7 @@ class json_pointer
// use unchecked array access; the const operator[] // use unchecked array access; the const operator[]
// asserts that the index exists // asserts that the index exists
const auto idx = array_index<BasicJsonType>(reference_token); ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
ptr = &(*ptr->m_data.m_value.array)[idx];
break; break;
} }
@@ -911,12 +733,12 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
const auto it = ptr->m_data.m_value.object->find(reference_token); const auto it = ptr->find(reference_token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
{ {
return nullptr; return nullptr;
} }
ptr = &it->second; ptr = &*it;
break; break;
} }
@@ -953,7 +775,7 @@ class json_pointer
{ {
return nullptr; return nullptr;
} }
ptr = &(*ptr->m_data.m_value.array)[idx]; ptr = &ptr->operator[](idx);
break; break;
} }
@@ -986,14 +808,13 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
const auto it = ptr->m_data.m_value.object->find(reference_token); if (!ptr->contains(reference_token))
if (it == ptr->m_data.m_value.object->end())
{ {
// we did not find the key in the object // we did not find the key in the object
return false; return false;
} }
ptr = &it->second; ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -1015,13 +836,13 @@ class json_pointer
return false; return false;
} }
if (idx >= ptr->m_data.m_value.array->size()) if (idx >= ptr->size())
{ {
// index out of range // index out of range
return false; return false;
} }
ptr = &(*ptr->m_data.m_value.array)[idx]; ptr = &ptr->operator[](idx);
break; break;
} }
@@ -1071,15 +892,6 @@ class json_pointer
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
} }
// every slash starts a reference token
const std::size_t size = reference_string.size();
std::size_t token_count = 0;
for (std::size_t i = 0; i < size; ++i)
{
token_count += (reference_string[i] == '/') ? 1 : 0;
}
result.reserve(token_count);
// extract the reference tokens: // extract the reference tokens:
// - slash: position of the last read slash (or end of string) // - slash: position of the last read slash (or end of string)
// - start: position after the previous slash // - start: position after the previous slash
@@ -1101,52 +913,31 @@ class json_pointer
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start; const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
auto reference_token = string_t(reference_string.data() + start, count); auto reference_token = string_t(reference_string.data() + start, count);
// check reference tokens are properly escaped; a token without // check reference tokens are properly escaped
// a '~' (the common case) needs neither check nor unescaping for (std::size_t pos = reference_token.find_first_of('~');
std::size_t pos = reference_token.find_first_of('~'); pos != string_t::npos;
if (pos != string_t::npos) pos = reference_token.find_first_of('~', pos + 1))
{ {
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1)) JSON_ASSERT(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
(reference_token[pos + 1] != '0' &&
reference_token[pos + 1] != '1')))
{ {
JSON_ASSERT(reference_token[pos] == '~'); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
// ~ must be followed by 0 or 1
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
(reference_token[pos + 1] != '0' &&
reference_token[pos + 1] != '1')))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
}
} }
detail::unescape(reference_token);
} }
// finally, store the reference token // finally, store the reference token
result.push_back(std::move(reference_token)); detail::unescape(reference_token);
result.push_back(reference_token);
} }
return result; return result;
} }
private: private:
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
// distinct, so objects that can append a key known to be absent
// (ordered_map) skip the lookup, which is linear and would make flatten
// quadratic; all other object types use operator[].
template<typename ObjectType, typename ValueType>
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, int /*unused*/)
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
{
obj.emplace_new_key(key, std::forward<ValueType>(v));
}
template<typename ObjectType, typename ValueType>
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, ...)
{
obj[key] = std::forward<ValueType>(v);
}
/*! /*!
@param[in] reference_string the reference string to the current value @param[in] reference_string the reference string to the current value
@param[in] value the value to consider @param[in] value the value to consider
@@ -1191,8 +982,7 @@ class json_pointer
// flatten `v`, whose path is `path`: primitives and empty containers // flatten `v`, whose path is `path`: primitives and empty containers
// are added to the result right away; other containers get a frame // are added to the result right away; other containers get a frame
auto& object = *result.m_data.m_value.object; const auto enter = [&stack, &path, &result](const BasicJsonType & v)
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
{ {
switch (v.type()) switch (v.type())
{ {
@@ -1201,7 +991,7 @@ class json_pointer
if (v.m_data.m_value.array->empty()) if (v.m_data.m_value.array->empty())
{ {
// flatten empty array as null // flatten empty array as null
add_flattened(object, path, nullptr, 0); result[path] = nullptr;
} }
else else
{ {
@@ -1215,7 +1005,7 @@ class json_pointer
if (v.m_data.m_value.object->empty()) if (v.m_data.m_value.object->empty())
{ {
// flatten empty object as null // flatten empty object as null
add_flattened(object, path, nullptr, 0); result[path] = nullptr;
} }
else else
{ {
@@ -1235,7 +1025,7 @@ class json_pointer
default: default:
{ {
// add a primitive value with its reference string // add a primitive value with its reference string
add_flattened(object, path, v, 0); result[path] = v;
return; return;
} }
} }
@@ -1281,10 +1071,6 @@ class json_pointer
enter(it->second); enter(it->second);
} }
} }
// the entries were added without going through basic_json, so the
// parent pointers (JSON_DIAGNOSTICS) have to be set now
result.set_parents();
} }
/*! /*!
@@ -1308,53 +1094,21 @@ class json_pointer
BasicJsonType result; BasicJsonType result;
// parse every key once; the reference tokens are kept for the second
// pass (the vectors are never resized, so the tree may refer to them)
std::vector<std::vector<string_t>> keys;
keys.reserve(value.m_data.m_value.object->size());
for (const auto& element : *value.m_data.m_value.object)
{
json_pointer ptr(element.first);
keys.push_back(std::move(ptr.reference_tokens));
}
// collect the pointer prefixes that have a reference token 0 among // collect the pointer prefixes that have a reference token 0 among
// their children; the values below them are arrays, all others are // their children; the values below them are arrays, all others are
// objects (see prefix_tree) // objects (see prefix_tree)
prefix_tree tree; prefix_tree tree;
// the number of the prefix with i + 1 tokens of the previous key; keys for (const auto& element : *value.m_data.m_value.object)
// are often sorted, so most of the prefix is the same as for the
// previous key and need not be looked up again
std::vector<std::size_t> path;
const std::vector<string_t>* previous = nullptr;
// the number of the complete prefix (i.e., the key) of each key
std::vector<std::size_t> leaves;
leaves.reserve(keys.size());
for (const auto& tokens : keys)
{ {
std::size_t common = 0; json_pointer ptr(element.first);
if (previous != nullptr) std::size_t id = 0;
for (auto& reference_token : ptr.reference_tokens)
{ {
const std::size_t limit = (std::min)(tokens.size(), previous->size()); id = tree.add_child(id, std::move(reference_token));
while (common < limit && tokens[common] == (*previous)[common])
{
++common;
}
} }
path.resize(common);
std::size_t id = common == 0 ? 0 : path[common - 1];
for (std::size_t i = common; i < tokens.size(); ++i)
{
id = tree.add_child(id, tokens[i]);
path.push_back(id);
}
leaves.push_back(id);
previous = &tokens;
} }
// iterate the JSON object values // iterate the JSON object values
std::vector<std::size_t> prefixes;
std::size_t key_index = 0;
for (const auto& element : *value.m_data.m_value.object) for (const auto& element : *value.m_data.m_value.object)
{ {
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
@@ -1362,21 +1116,11 @@ class json_pointer
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
} }
const auto& tokens = keys[key_index];
// number the prefixes of this key from its last one to the first
prefixes.resize(tokens.size());
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i)
{
id = tree.parent[id];
prefixes[i - 1] = id;
}
++key_index;
// Assign the value to the reference pointed to by JSON pointer. Note // Assign the value to the reference pointed to by JSON pointer. Note
// that if the JSON pointer is "" (i.e., points to the whole value), // that if the JSON pointer is "" (i.e., points to the whole value),
// function get_and_create returns a reference to the result itself. // function get_and_create returns a reference to the result itself.
// An assignment will then create a primitive value. // An assignment will then create a primitive value.
get_and_create(result, tokens, prefixes, tree) = element.second; json_pointer(element.first).get_and_create(result, tree) = element.second;
} }
return result; return result;
@@ -1415,7 +1159,7 @@ class json_pointer
= delete; = delete;
#else #else
{ {
return equals_string(rhs); return *this == json_pointer(rhs);
} }
#endif #endif
@@ -1499,7 +1243,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
= delete; = delete;
#else #else
{ {
return lhs.equals_string(rhs); return lhs == json_pointer<RefStringTypeLhs>(rhs);
} }
#endif #endif
@@ -1512,7 +1256,7 @@ inline bool operator==(const StringType& lhs,
= delete; = delete;
#else #else
{ {
return rhs.equals_string(lhs); return json_pointer<RefStringTypeRhs>(lhs) == rhs;
} }
#endif #endif
+24 -32
View File
@@ -9,7 +9,6 @@
#pragma once #pragma once
#include <cstddef> // size_t #include <cstddef> // size_t
#include <utility> // move
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
@@ -25,46 +24,39 @@ namespace detail
* Note the order of escaping "~" to "~0" and "/" to "~1" is important. * Note the order of escaping "~" to "~0" and "/" to "~1" is important.
* *
* The string is rebuilt in a single pass, appending whole runs between the * The string is rebuilt in a single pass, appending whole runs between the
* characters that need escaping, while repeated replace() calls would move * characters that need escaping. Scanning with find_first_of() keeps the
* the tail of the string once per escaped character. @ref escape_into appends * common case -- nothing to escape -- as fast as a single search, while
* the escaped string to an existing one. * repeated replace() calls would move the tail of the string once per
* escaped character.
*/ */
template<typename StringType>
inline void escape_into(StringType& out, const StringType& s)
{
const std::size_t size = s.size();
std::size_t run = 0;
for (std::size_t i = 0; i < size; ++i)
{
const char c = s[i];
if (c == '~' || c == '/')
{
out.append(s.data() + run, i - run);
out.append(c == '~' ? "~0" : "~1", 2);
run = i + 1;
}
}
out.append(s.data() + run, size - run);
}
template<typename StringType> template<typename StringType>
inline StringType escape(const StringType& s) inline StringType escape(const StringType& s)
{ {
// a single scan for both special characters; nothing to escape is the common case auto next_special = [&s](std::size_t from)
const std::size_t size = s.size();
std::size_t i = 0;
while (i < size && s[i] != '~' && s[i] != '/')
{ {
++i; const auto tilde = s.find_first_of('~', from);
} const auto slash = s.find_first_of('/', from);
if (i == size) return tilde < slash ? tilde : slash; // npos is the largest value
};
auto pos = next_special(0);
if (pos == StringType::npos)
{ {
return s; return s;
} }
StringType result; StringType result;
result.reserve(size + 2); result.reserve(s.size() + 2);
escape_into(result, s);
std::size_t run = 0;
while (pos != StringType::npos)
{
result.append(s.data() + run, pos - run);
result.append(s[pos] == '~' ? "~0" : "~1", 2);
run = pos + 1;
pos = next_special(run);
}
result.append(s.data() + run, s.size() - run);
return result; return result;
} }
@@ -109,7 +101,7 @@ inline void unescape(StringType& s)
pos = s.find_first_of('~', run); pos = s.find_first_of('~', run);
} }
result.append(s.data() + run, s.size() - run); result.append(s.data() + run, s.size() - run);
s = std::move(result); s = result;
} }
} // namespace detail } // namespace detail
+120 -345
View File
@@ -585,12 +585,26 @@ 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:
{ {
// allocating: kept out of line, so the scalar cases below inline binary = create<binary_t>();
init_owned(t);
break; break;
} }
@@ -637,34 +651,6 @@ 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)) {}
@@ -925,18 +911,7 @@ 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)
{ {
@@ -1104,64 +1079,51 @@ public:
return next; return next;
} }
/// @brief the capacity of the stored array or object, or unknown_size() /// @brief the capacity of the stored array, 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. Containers that do not have a /// would invalidate the parent pointers. Array types that do not have a
/// capacity() member function report unknown_size() if their elements may /// capacity() member function report unknown_size(), which is treated as
/// move (arrays, ordered_map) and 0 if they never do (std::map). /// "the elements may have moved".
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
template < typename C, detail::enable_if_t < detail::has_capacity<C>::value, int > = 0 > template < typename A = array_t, detail::enable_if_t < detail::has_capacity<A>::value, int > = 0 >
static std::size_t capacity_of(const C& container) noexcept std::size_t array_capacity() const noexcept
{ {
return container.capacity(); return m_data.m_value.array->capacity();
} }
template < typename C, detail::enable_if_t < !detail::has_capacity<C>::value, int > = 0 > template < typename A = array_t, detail::enable_if_t < !detail::has_capacity<A>::value, int > = 0 >
static std::size_t capacity_of(const C& /*container*/) noexcept std::size_t array_capacity() const noexcept
{ {
return std::is_same<C, array_t>::value || detail::is_ordered_map<C>::value ? detail::unknown_size() : 0; return detail::unknown_size();
}
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 container_capacity() noexcept static constexpr std::size_t array_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 or object /// @brief set the parent of a value that has just been added to an array
/// @param j the added (or looked-up) value /// @param j the added value
/// @param old_capacity the value @ref container_capacity() returned before the /// @param old_capacity the value @ref array_capacity() returned before the
/// insertion /// insertion
reference set_parent_after_insert(reference j, std::size_t old_capacity) reference set_parent_after_array_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()
|| container_capacity() != old_capacity)) || array_capacity() != old_capacity))
{ {
// the capacity has changed, or the container type does not let us // the capacity has changed, or the array type does not let us tell:
// tell: the elements may have moved, so update all parents // 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_at(j); return set_parent(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)
@@ -2584,17 +2546,18 @@ public:
// check of passed value is valid // check of passed value is valid
other.assert_invariant(); other.assert_invariant();
if (JSON_HEDLEY_LIKELY(m_data.m_type != value_t::object && m_data.m_type != value_t::array if (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))
{ {
// null, boolean, numbers, discarded: plain copy of the union // copying the container directly would call this constructor again
m_data.m_value = other.m_data.m_value; // for every element, once per nesting level
copy_structured(other);
} }
else else
{ {
copy_owning(other); copy_leaf_value(other, *this);
} }
set_parents();
assert_invariant(); assert_invariant();
} }
@@ -2824,22 +2787,6 @@ 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
{ {
@@ -3498,7 +3445,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_at(array_at(*this, idx)); return set_parent(array_at(*this, idx));
} }
/// @brief access specified array element with bounds checking /// @brief access specified array element with bounds checking
@@ -3512,7 +3459,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_at(object_at(*this, key)); return set_parent(object_at(*this, key));
} }
/// @brief access specified object element with bounds checking /// @brief access specified object element with bounds checking
@@ -3521,7 +3468,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_at(object_at(*this, std::forward<KeyType>(key))); return set_parent(object_at(*this, std::forward<KeyType>(key)));
} }
/// @brief access specified object element with bounds checking /// @brief access specified object element with bounds checking
@@ -3566,13 +3513,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 = container_capacity(); const auto old_capacity = array_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()
|| container_capacity() != old_capacity)) || array_capacity() != old_capacity))
{ {
// capacity has changed: update all parents // capacity has changed: update all parents
set_parents(); set_parents();
@@ -3619,9 +3566,8 @@ 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_after_insert(result.first->second, old_capacity); return set_parent(result.first->second);
} }
throw_subscript_wrong_type("string"); throw_subscript_wrong_type("string");
@@ -3671,9 +3617,8 @@ 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_after_insert(result.first->second, old_capacity); return set_parent(result.first->second);
} }
throw_subscript_wrong_type("string"); throw_subscript_wrong_type("string");
@@ -4579,9 +4524,9 @@ public:
} }
// add the element to the array (move semantics) // add the element to the array (move semantics)
const auto old_capacity = container_capacity(); const auto old_capacity = array_capacity();
m_data.m_value.array->push_back(std::move(val)); m_data.m_value.array->push_back(std::move(val));
set_parent_after_insert(m_data.m_value.array->back(), old_capacity); set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
// if val is moved from, basic_json move constructor marks it null, so we do not call the destructor // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
} }
@@ -4610,9 +4555,9 @@ public:
} }
// add the element to the array // add the element to the array
const auto old_capacity = container_capacity(); const auto old_capacity = array_capacity();
m_data.m_value.array->push_back(val); m_data.m_value.array->push_back(val);
set_parent_after_insert(m_data.m_value.array->back(), old_capacity); set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
} }
/// @brief add an object to an array /// @brief add an object to an array
@@ -4640,9 +4585,8 @@ 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_after_insert(res.first->second, old_capacity); set_parent(res.first->second);
} }
/// @brief add an object to an object /// @brief add an object to an object
@@ -4695,9 +4639,9 @@ public:
} }
// add the element to the array (perfect forwarding) // add the element to the array (perfect forwarding)
const auto old_capacity = container_capacity(); const auto old_capacity = array_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_insert(m_data.m_value.array->back(), old_capacity); return set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
} }
/// @brief add an object to an object if key does not exist /// @brief add an object to an object if key does not exist
@@ -4718,9 +4662,8 @@ 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_after_insert(res.first->second, old_capacity); set_parent(res.first->second);
// 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();
@@ -5019,9 +4962,9 @@ public:
continue; continue;
} }
} }
// operator[] repairs the other members if ordered_json relocates // set_parent() also repairs the other members, which ordered_json
// them; assigning through it keeps this O(1) per key // relocates when adding a key makes its vector grow
operator[](it.key()) = std::move(it.value()); set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
} }
} }
@@ -5071,8 +5014,9 @@ public:
continue; continue;
} }
} }
// operator[] repairs the other members if ordered_json relocates them // set_parent() also repairs the other members, which ordered_json
target->operator[](first.key()) = std::move(first.value()); // relocates when adding a key makes its vector grow
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
++first; ++first;
} }
} }
@@ -5214,20 +5158,6 @@ 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
@@ -5264,11 +5194,24 @@ 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); \
} \ } \
\ \
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \ case value_t::null: \
\ 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); \
\ \
@@ -5312,24 +5255,6 @@ 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
@@ -5357,168 +5282,6 @@ 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
@@ -5530,11 +5293,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_SCALAR_FAST( ==, true) JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
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
@@ -5551,8 +5314,13 @@ 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;
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD* // default_result is used if we cannot compare values. In that case,
return three_way_general(lhs, rhs); // 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)
} }
/// @brief comparison: 3-way /// @brief comparison: 3-way
@@ -5640,11 +5408,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_SCALAR_FAST( ==, true) JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
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
@@ -5699,8 +5467,11 @@ 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
{ {
JSON_IMPLEMENT_SCALAR_FAST( <, false) // default_result is used if we cannot compare values. In that case,
return less_general(lhs, rhs); // 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)
} }
/// @brief comparison: less than /// @brief comparison: less than
@@ -5811,8 +5582,6 @@ public:
#endif #endif
#undef JSON_IMPLEMENT_OPERATOR #undef JSON_IMPLEMENT_OPERATOR
#undef JSON_IMPLEMENT_SCALAR_FAST
#undef JSON_IMPLEMENT_SCALAR_CASES
/// @} /// @}
@@ -6793,18 +6562,24 @@ public:
}; };
// wrapper for "add" operation; add value at ptr // wrapper for "add" operation; add value at ptr
const auto operation_add = [&result](json_pointer & ptr, basic_json val) const auto operation_add = [&result](json_pointer & ptr, const basic_json & val)
{ {
// adding to the root of the target document means replacing it // adding to the root of the target document means replacing it
if (ptr.empty()) if (ptr.empty())
{ {
result = std::move(val); result = val;
return; return;
} }
// get reference to the parent of the JSON pointer ptr; a missing // make sure the top element of the pointer exists
// parent (or one of its ancestors) is reported by at() json_pointer const top_pointer = ptr.top();
auto last_path = std::move(ptr.reference_tokens.back()); if (top_pointer != ptr)
{
result.at(top_pointer);
}
// get reference to the parent of the JSON pointer ptr
const auto last_path = ptr.back();
ptr.pop_back(); ptr.pop_back();
// parent must exist when performing patch add per RFC6902 specs // parent must exist when performing patch add per RFC6902 specs
basic_json& parent = result.at(ptr); basic_json& parent = result.at(ptr);
@@ -6815,7 +6590,7 @@ public:
case value_t::object: case value_t::object:
{ {
// use operator[] to add value // use operator[] to add value
parent[std::move(last_path)] = std::move(val); parent[last_path] = val;
break; break;
} }
@@ -6824,7 +6599,7 @@ public:
if (last_path == "-") if (last_path == "-")
{ {
// special case: append to back // special case: append to back
parent.push_back(std::move(val)); parent.push_back(val);
} }
else else
{ {
@@ -6836,7 +6611,7 @@ public:
} }
// default case: insert add offset // default case: insert add offset
parent.insert(parent.begin() + static_cast<difference_type>(idx), std::move(val)); parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
} }
break; break;
} }
@@ -6859,8 +6634,7 @@ public:
const auto operation_remove = [this, & result](json_pointer & ptr) const auto operation_remove = [this, & result](json_pointer & ptr)
{ {
// get reference to the parent of the JSON pointer ptr // get reference to the parent of the JSON pointer ptr
static_cast<void>(ptr.back()); // throws out_of_range.405 if ptr is the root const auto last_path = ptr.back();
const auto last_path = std::move(ptr.reference_tokens.back());
ptr.pop_back(); ptr.pop_back();
basic_json& parent = result.at(ptr); basic_json& parent = result.at(ptr);
@@ -6960,8 +6734,8 @@ public:
} }
// collect mandatory members // collect mandatory members
const string_t& op = *get_value("op", "op", true).m_data.m_value.string; const auto op = get_value("op", "op", true).template get<string_t>();
const string_t& path = *get_value(op, "path", true).m_data.m_value.string; const auto path = get_value(op, "path", true).template get<string_t>();
json_pointer ptr(path); json_pointer ptr(path);
switch (get_op(op)) switch (get_op(op))
@@ -6987,7 +6761,7 @@ public:
case patch_operations::move: case patch_operations::move:
{ {
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string; const auto from_path = get_value("move", "from", true).template get<string_t>();
json_pointer from_ptr(from_path); json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
@@ -6996,28 +6770,29 @@ public:
} }
// the "from" location must exist - use at() // the "from" location must exist - use at()
// a root "from" is rejected by operation_remove below, which must leave result intact basic_json const v = result.at(from_ptr);
basic_json v = from_ptr.empty() ? basic_json(result) : std::move(result.at(from_ptr));
// The move operation is functionally identical to a // The move operation is functionally identical to a
// "remove" operation on the "from" location, followed // "remove" operation on the "from" location, followed
// immediately by an "add" operation at the target // immediately by an "add" operation at the target
// location with the value that was just removed. // location with the value that was just removed.
operation_remove(from_ptr); operation_remove(from_ptr);
operation_add(ptr, std::move(v)); operation_add(ptr, v);
break; break;
} }
case patch_operations::copy: case patch_operations::copy:
{ {
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string; const auto from_path = get_value("copy", "from", true).template get<string_t>();
const json_pointer from_ptr(from_path); const json_pointer from_ptr(from_path);
// the "from" location must exist - use at()
basic_json const v = result.at(from_ptr);
// The copy is functionally identical to an "add" // The copy is functionally identical to an "add"
// operation at the target location using the value // operation at the target location using the value
// specified in the "from" member; the "from" location // specified in the "from" member.
// must exist - use at() operation_add(ptr, v);
operation_add(ptr, result.at(from_ptr));
break; break;
} }
-11
View File
@@ -143,17 +143,6 @@ public:
return {std::prev(this->end()), true}; return {std::prev(this->end()), true};
} }
/// @brief add an element at the end without checking whether the key exists
/// @note The caller must guarantee that @a key is not yet contained, otherwise
/// the container holds duplicate keys. This avoids the linear search of
/// emplace() and operator[] when many keys known to be new are added.
template<class V, detail::enable_if_t<
detail::is_constructible<T, V>::value, int> = 0>
void emplace_new_key(const key_type& key, V && t)
{
append(key, std::forward<V>(t));
}
T& operator[](const key_type& key) T& operator[](const key_type& key)
{ {
return emplace(key, T{}).first->second; return emplace(key, T{}).first->second;
+245 -862
View File
File diff suppressed because it is too large. Load diff
-26
View File
@@ -9,10 +9,7 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream> #include <fstream>
#include <array>
#include <deque>
#include <numeric> #include <numeric>
#include <set>
#include <vector> #include <vector>
#include <test_data.hpp> #include <test_data.hpp>
@@ -624,27 +621,4 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000); BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
// conversion of a JSON array into standard containers
template<typename Container>
static void GetArray(benchmark::State& state)
{
json j = json::array();
for (int i = 0; i < 1000; ++i)
{
j.push_back(i);
}
for (auto _ : state)
{
auto c = j.get<Container>();
benchmark::DoNotOptimize(c);
}
state.SetItemsProcessed(state.iterations() * 1000);
}
BENCHMARK_TEMPLATE(GetArray, std::vector<int>);
BENCHMARK_TEMPLATE(GetArray, std::deque<int>);
BENCHMARK_TEMPLATE(GetArray, std::set<int>);
BENCHMARK_TEMPLATE(GetArray, std::array<int, 1000>);
BENCHMARK_MAIN(); BENCHMARK_MAIN();
-1
View File
@@ -9,7 +9,6 @@
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <algorithm> #include <algorithm>
#include <numeric>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
-155
View File
@@ -19,7 +19,6 @@
#include <cctype> #include <cctype>
#include <cstdint> #include <cstdint>
#include <limits>
#include <map> #include <map>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -1150,157 +1149,3 @@ 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());
}
}
-139
View File
@@ -8,9 +8,6 @@
#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;
@@ -187,139 +184,3 @@ TEST_CASE("other constructors and destructor")
} }
} }
} }
TEST_CASE("copy constructor and destructor across value types")
{
SECTION("copy of a scalar is independent of the source")
{
const json src_null = nullptr;
const json src_bool = true;
const json src_int = -7;
const json src_uint = 7u;
const json src_float = 2.5;
json c_null(src_null);
json c_bool(src_bool);
json c_int(src_int);
json c_uint(src_uint);
json c_float(src_float);
CHECK(c_null.is_null());
CHECK(c_bool == true);
CHECK(c_int == -7);
CHECK(c_uint == 7u);
CHECK(c_float == 2.5);
c_int = 8;
c_float = 3.5;
CHECK(src_int == -7);
CHECK(src_float == 2.5);
}
SECTION("copy of an owning value, then mutate the copy")
{
const json src_string = "hello";
const json src_array = {1, 2, 3};
const json src_object = {{"a", 1}, {"b", {{"c", 2}}}};
const json src_binary = json::binary({1, 2, 3});
json c_string(src_string);
json c_array(src_array);
json c_object(src_object);
json c_binary(src_binary);
CHECK(c_string == src_string);
CHECK(c_array == src_array);
CHECK(c_object == src_object);
CHECK(c_binary == src_binary);
c_string.get_ref<json::string_t&>() += " world";
c_array.push_back(4);
c_object["b"]["c"] = 3;
c_object["d"] = 4;
c_binary.get_binary().push_back(4);
CHECK(src_string == "hello");
CHECK(src_array.size() == 3);
CHECK(src_object["b"]["c"] == 2);
CHECK_FALSE(src_object.contains("d"));
CHECK(src_binary.get_binary().size() == 3);
CHECK(c_string == "hello world");
CHECK(c_array.size() == 4);
CHECK(c_object["b"]["c"] == 3);
CHECK(c_binary.get_binary().size() == 4);
}
SECTION("copy keeps parent pointers valid (JSON_DIAGNOSTICS)")
{
const json src = {{"a", {1, 2}}, {"b", "x"}};
json c(src);
c["a"].push_back(3);
CHECK(c["a"].size() == 3);
CHECK(src["a"].size() == 2);
}
SECTION("vector of mixed types: build, copy, and destroy")
{
{
std::vector<json> v;
for (int i = 0; i < 70; ++i)
{
switch (i % 9)
{
case 0:
v.emplace_back(nullptr);
break;
case 1:
v.emplace_back(i % 2 == 0);
break;
case 2:
v.emplace_back(-i);
break;
case 3:
v.emplace_back(static_cast<json::number_unsigned_t>(i));
break;
case 4:
v.emplace_back(i * 0.5);
break;
case 5:
v.emplace_back(std::string(static_cast<std::size_t>(i), 'x'));
break;
case 6:
v.emplace_back(json::array({i, "s", nullptr}));
break;
case 7:
v.emplace_back(json::object({{"k", i}}));
break;
default:
v.emplace_back(json::binary({1, 2, 3}));
break;
}
}
REQUIRE(v.size() == 70);
std::vector<json> copy(v);
CHECK(copy == v);
// mutating the copy leaves the original alone
copy[5] = "changed";
copy[6].push_back(1);
CHECK(copy != v);
CHECK(v[5] == std::string(5, 'x'));
CHECK(v[6].size() == 3);
v.erase(v.begin(), v.begin() + 10);
CHECK(v.size() == 60);
}
// destroying the discarded and default-constructed values
std::vector<json> w(4);
w.emplace_back(json::value_t::discarded);
w.emplace_back(json::value_t::object);
w.emplace_back(json::value_t::array);
w.emplace_back(json::value_t::string);
w.emplace_back(json::value_t::binary);
CHECK(w.size() == 9);
}
}
-174
View File
@@ -13,11 +13,9 @@
#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>
@@ -633,175 +631,3 @@ 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");
}
}
+447
View File
@@ -0,0 +1,447 @@
// __ _____ _____ _____
// __| | __| | | | 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
// The fast paths for contiguous input (bulk string scanning, SWAR searches,
// UTF-8 prefix validation, the 8-digit number loop, the 8-byte indentation
// skip, the BSON/BON8 bulk string readers) load several bytes at a time. This
// test places every input directly in front of (or directly behind) an
// inaccessible page, so reading even one byte outside the input crashes, with
// or without sanitizers. ASan alone misses over-reads into the slack of the
// same heap allocation.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cstdint>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#if defined(_WIN32)
#define JSON_TEST_GUARD_PAGE_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#elif defined(__unix__) || defined(__APPLE__)
#define JSON_TEST_GUARD_PAGE_POSIX
#include <sys/mman.h>
#include <unistd.h>
#endif
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS) || defined(JSON_TEST_GUARD_PAGE_POSIX)
namespace
{
// Three pages: an inaccessible one, a readable one, an inaccessible one. An
// input is copied either to the end of the middle page (any read past its last
// byte faults) or to its start (any read before its first byte faults).
class guarded_buffer
{
public:
guarded_buffer()
: page(page_size())
{
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
void* const p = VirtualAlloc(nullptr, 3 * page, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
REQUIRE(p != nullptr);
base = static_cast<unsigned char*>(p);
DWORD old_protect = 0;
REQUIRE(VirtualProtect(base + page, page, PAGE_READWRITE, &old_protect) != 0);
#else
void* const p = mmap(nullptr, 3 * page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
REQUIRE(p != MAP_FAILED);
base = static_cast<unsigned char*>(p);
REQUIRE(mprotect(base + page, page, PROT_READ | PROT_WRITE) == 0);
#endif
}
~guarded_buffer()
{
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
VirtualFree(base, 0, MEM_RELEASE);
#else
munmap(base, 3 * page);
#endif
}
guarded_buffer(const guarded_buffer&) = delete;
guarded_buffer& operator=(const guarded_buffer&) = delete;
guarded_buffer(guarded_buffer&&) = delete;
guarded_buffer& operator=(guarded_buffer&&) = delete;
// copies @a input so its last byte is the last readable byte
const std::uint8_t* at_end(const std::string& input)
{
REQUIRE(input.size() <= page);
unsigned char* const first = base + (2 * page) - input.size();
std::memcpy(first, input.data(), input.size());
return first;
}
// copies @a input so its first byte is the first readable byte
const std::uint8_t* at_start(const std::string& input)
{
REQUIRE(input.size() <= page);
unsigned char* const first = base + page;
std::memcpy(first, input.data(), input.size());
return first;
}
private:
static std::size_t page_size()
{
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
SYSTEM_INFO info;
GetSystemInfo(&info);
return static_cast<std::size_t>(info.dwPageSize);
#else
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
#endif
}
std::size_t page = 0;
unsigned char* base = nullptr;
};
// the serialization of the result of @a f, or the exception message; @a f
// takes the allow_exceptions flag, which is false without exceptions, where
// an error yields a discarded value instead
template<typename F>
std::string outcome(F f)
{
#if defined(JSON_NOEXCEPTION)
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
#else
try
{
return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
}
catch (const json::exception& e)
{
return std::string("error: ") + e.what();
}
#endif
}
// a SAX handler that only counts events, so sax_parse runs without a DOM
struct counting_sax : nlohmann::json_sax<json>
{
std::size_t events = 0;
std::string error{}; // NOLINT(readability-redundant-member-init)
bool null() override
{
return count();
}
bool boolean(bool /*val*/) override
{
return count();
}
bool number_integer(number_integer_t /*val*/) override
{
return count();
}
bool number_unsigned(number_unsigned_t /*val*/) override
{
return count();
}
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
{
return count();
}
bool string(string_t& /*val*/) override
{
return count();
}
bool binary(binary_t& /*val*/) override
{
return count();
}
bool start_object(std::size_t /*elements*/) override
{
return count();
}
bool key(string_t& /*val*/) override
{
return count();
}
bool end_object() override
{
return count();
}
bool start_array(std::size_t /*elements*/) override
{
return count();
}
bool end_array() override
{
return count();
}
bool parse_error(std::size_t position, const std::string& /*last_token*/, const nlohmann::detail::exception& ex) override
{
error = std::to_string(position) + " " + ex.what();
return false;
}
private:
bool count()
{
++events;
return true;
}
};
// all prefixes of each seed, so the input ends at every byte of it
std::vector<std::string> all_prefixes(const std::vector<std::string>& seeds)
{
std::vector<std::string> result;
for (const auto& seed : seeds)
{
for (std::size_t n = 0; n <= seed.size(); ++n)
{
result.push_back(seed.substr(0, n));
}
}
return result;
}
std::vector<std::string> json_seeds()
{
std::vector<std::string> seeds =
{
// string runs: ASCII, escapes, \u escapes, surrogate pairs
"\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"",
R"("ab\ncd\"ef\\gh\u00e4ij\uD83D\uDE00kl\u12")",
// 2-, 3- and 4-byte UTF-8 sequences, then invalid UTF-8
std::string("\"\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80") + "abcdefgh\"",
"\"abcdefgh\xC3\x28 \xED\xA0\x80 \xF4\x90\x80\x80 \xF8 \xC0\xAF\"",
std::string("\"abcdefg\x01") + "abcdefgh\"",
// numbers, so the 8-digit loop runs up to the end
"12345678",
"1234567890123456",
"12345678901234567890123",
"-0.000000001234567890123456789",
"3.14159265358979323846264338327950288e+100",
"-1.2345678901234567e-12",
"1E-5",
"[1.5,2e10,-3E-5,0.000000001,123456789012345678]",
// literals
"[true,false,null]",
// comments (only parsed with ignore_comments)
"/* comment */ [1, // line comment\n 2] /**/",
// a pretty-printed document
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(4),
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(8, '\t'),
};
// indentation runs of every length, with and without a closing bracket
for (std::size_t n = 0; n <= 20; ++n)
{
seeds.push_back("[\n" + std::string(n, ' ') + "1,\n" + std::string(n, ' ') + "2\n]");
}
return all_prefixes(seeds);
}
// a value whose binary serializations contain long strings and keys, numbers
// of every width, and nested containers
json binary_document()
{
json j;
j["a key that is longer than eight bytes"] = "a string value that is longer than eight bytes";
j["\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80"] = "\xF0\x9F\x98\x80\xE2\x82\xAC\xC3\xA4 utf-8 0123456789";
j["numbers"] = {0, 1, -1, 255, -256, 65536, -2147483649LL, 4294967296ULL, 1.5, -2.25e100};
j["nested"] = {{"x", {{"y", {true, false, nullptr}}}}};
j["s"] = "";
return j;
}
// readers for check_binary(), callable with a container or an iterator range
// followed by the allow_exceptions flag
#define JSON_TEST_GUARD_PAGE_READER(name, from) \
struct name \
{ \
template<typename... Args> \
json operator()(bool allow_exceptions, Args&& ... args) const \
{ \
return json::from(std::forward<Args>(args)..., true, allow_exceptions); \
} \
};
JSON_TEST_GUARD_PAGE_READER(read_bson, from_bson)
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
JSON_TEST_GUARD_PAGE_READER(read_cbor, from_cbor)
JSON_TEST_GUARD_PAGE_READER(read_msgpack, from_msgpack)
JSON_TEST_GUARD_PAGE_READER(read_ubjson, from_ubjson)
JSON_TEST_GUARD_PAGE_READER(read_bjdata, from_bjdata)
#undef JSON_TEST_GUARD_PAGE_READER
// reads every prefix of @a bytes at both ends of the guarded page and from a
// std::string, which must give the same outcome
template<typename Reader>
void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes, Reader parse)
{
for (const auto& input : all_prefixes({std::string(bytes.begin(), bytes.end())}))
{
CAPTURE(input)
const std::string expected = outcome([&](bool allow)
{
return parse(allow, input);
});
for (int placement = 0; placement < 2; ++placement)
{
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
CHECK(outcome([&](bool allow)
{
return parse(allow, first, first + input.size());
}) == expected);
}
}
}
} // namespace
TEST_CASE("guard page")
{
guarded_buffer guard;
SECTION("string scanning")
{
// fills that drive each function to a different stop
const std::vector<std::string> fills =
{
std::string(64, 'a'),
std::string(64, '"'),
std::string(64, '\\'),
std::string(64, '\x01'),
std::string(64, '\x7F'),
std::string(64, '\xFF'),
};
std::vector<std::string> patterns = fills;
{
std::string utf8;
while (utf8.size() < 64)
{
utf8 += "\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80" "ab";
}
patterns.push_back(utf8);
}
{
std::string bytes;
for (unsigned int i = 0; i < 64; ++i)
{
bytes.push_back(static_cast<char>((i * 37u) & 0xFFu));
}
patterns.push_back(bytes);
}
for (const auto& pattern : patterns)
{
for (std::size_t n = 0; n <= 64; ++n)
{
const std::string input = pattern.substr(0, n);
for (int placement = 0; placement < 2; ++placement)
{
const unsigned char* const p = placement == 0 ? guard.at_end(input) : guard.at_start(input);
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
#endif
CHECK(nlohmann::detail::string_bulk_run(p, n) <= n);
CHECK(nlohmann::detail::scalar_string_bulk_run(p, n) <= n);
for (std::size_t i = 0; i < n; ++i)
{
CHECK(nlohmann::detail::validate_one_utf8(p + i, n - i) <= n - i);
}
}
}
}
}
SECTION("Eisel-Lemire")
{
const std::vector<std::string> numbers = all_prefixes(
{
"1234567812345678123456781234567812345678",
"-1.2345678901234567890123456789e-123",
"0.00000000000000000000000000000000000001",
"98765432.10987654",
});
for (const auto& number : numbers)
{
double out = 0;
const char* const end = reinterpret_cast<const char*>(guard.at_end(number));
static_cast<void>(nlohmann::detail::parse_float_eisel_lemire(end, end + number.size(), out));
const char* const start = reinterpret_cast<const char*>(guard.at_start(number));
static_cast<void>(nlohmann::detail::parse_float_eisel_lemire(start, start + number.size(), out));
}
}
SECTION("JSON")
{
for (const auto& input : json_seeds())
{
CAPTURE(input)
const std::string expected = outcome([&](bool allow)
{
return json::parse(input, nullptr, allow);
});
const std::string expected_comments = outcome([&](bool allow)
{
return json::parse(input, nullptr, allow, true);
});
const bool expected_accept = json::accept(input);
counting_sax expected_sax;
const bool expected_sax_result = json::sax_parse(input, &expected_sax);
for (int placement = 0; placement < 2; ++placement)
{
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
const std::uint8_t* const last = first + input.size();
CHECK(outcome([&](bool allow)
{
return json::parse(first, last, nullptr, allow);
}) == expected);
CHECK(outcome([&](bool allow)
{
return json::parse(first, last, nullptr, allow, true);
}) == expected_comments);
CHECK(json::accept(first, last) == expected_accept);
counting_sax sax;
CHECK(json::sax_parse(first, last, &sax) == expected_sax_result);
CHECK(sax.events == expected_sax.events);
CHECK(sax.error == expected_sax.error);
}
}
}
SECTION("binary formats")
{
// every prefix ends inside a value, e.g., inside a BSON key or BON8
// string without its terminator
const json j = binary_document();
check_binary(guard, json::to_bson(j), read_bson());
check_binary(guard, json::to_bon8(j), read_bon8());
check_binary(guard, json::to_cbor(j), read_cbor());
check_binary(guard, json::to_msgpack(j), read_msgpack());
check_binary(guard, json::to_ubjson(j), read_ubjson());
check_binary(guard, json::to_bjdata(j), read_bjdata());
}
}
#endif
-245
View File
@@ -23,14 +23,8 @@ using nlohmann::json;
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace) using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif #endif
#include <cerrno>
#include <cstdlib>
#include <limits>
#include <map> #include <map>
#include <random>
#include <sstream> #include <sstream>
#include <string>
#include <vector>
TEST_CASE("JSON pointers") TEST_CASE("JSON pointers")
{ {
@@ -1077,242 +1071,3 @@ TEST_CASE("flatten of structured values")
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})"); R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
} }
} }
TEST_CASE("flatten of ordered_json")
{
using nlohmann::ordered_json;
SECTION("keeps the document order of the leaves")
{
const ordered_json j = ordered_json::parse(R"({
"z": 1, "a": {"y": [true, null, {}], "b": "x", "": []},
"m~n": 2.5, "o/p": {"k": {}}, "0": [[1], [2, 3]]
})");
const ordered_json flat = j.flatten();
std::vector<std::string> keys;
for (const auto& el : flat.items())
{
keys.push_back(el.key());
}
const std::vector<std::string> expected =
{
"/z", "/a/y/0", "/a/y/1", "/a/y/2", "/a/b", "/a/", "/m~0n", "/o~1p/k", "/0/0/0", "/0/1/0", "/0/1/1"
};
CHECK(keys == expected);
CHECK(flat.size() == expected.size());
CHECK(flat["/m~0n"] == 2.5);
CHECK(flat["/a/y/2"].is_null());
}
SECTION("round-trips and agrees with json")
{
ordered_json j;
for (int i = 0; i < 200; ++i)
{
j["k" + std::to_string(i)] = {{"v", i}, {"l", {i, i + 1, "s"}}};
}
const ordered_json flat = j.flatten();
CHECK(flat.size() == 800);
CHECK(flat.unflatten() == j);
// same entries as for a sorted object
const nlohmann::json plain = nlohmann::json::parse(j.dump());
CHECK(nlohmann::json::parse(flat.dump()) == plain.flatten());
}
SECTION("primitive and empty root")
{
CHECK(ordered_json(1).flatten() == ordered_json({{"", 1}}));
CHECK(ordered_json::array().flatten() == ordered_json({{"", nullptr}}));
}
}
namespace
{
// reference implementation of the array index rules (RFC 6901, Sect. 4) based on strtoull
enum class index_class
{
ok,
leading_zero,
not_a_number,
unresolved,
exceeds_size_type
};
index_class classify_index(const std::string& s, std::size_t& idx)
{
if (s.size() > 1 && s[0] == '0')
{
return index_class::leading_zero;
}
if (!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9'))
{
return index_class::not_a_number;
}
char* end = nullptr;
errno = 0;
const unsigned long long res = std::strtoull(s.c_str(), &end, 10); // NOLINT(runtime/int)
if (end == s.c_str() || errno == ERANGE || static_cast<std::size_t>(end - s.c_str()) != s.size())
{
return index_class::unresolved;
}
if (res >= static_cast<unsigned long long>((std::numeric_limits<std::size_t>::max)())) // NOLINT(runtime/int)
{
return index_class::exceeds_size_type;
}
idx = static_cast<std::size_t>(res);
return index_class::ok;
}
// the observable behavior of json_pointer::at() and contains() for an array index token
void check_index_token(const std::string& token)
{
const json array = {10, 20, 30};
const json::json_pointer ptr("/" + token);
std::size_t idx = 0;
const auto cls = classify_index(token, idx);
const bool exists = cls == index_class::ok && idx < array.size();
CAPTURE(token)
CHECK(array.contains(ptr) == (exists && token != "-"));
if (token == "-")
{
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
return;
}
switch (cls)
{
case index_class::leading_zero:
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
break;
case index_class::not_a_number:
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
break;
case index_class::unresolved:
case index_class::exceeds_size_type:
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
break;
case index_class::ok:
default:
if (exists)
{
CHECK(array.at(ptr) == array[idx]);
}
else
{
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
}
break;
}
}
} // namespace
TEST_CASE("JSON pointer array index tokens match strtoull semantics")
{
SECTION("boundary values")
{
const std::string max_digits = std::to_string((std::numeric_limits<std::size_t>::max)());
const std::vector<std::string> tokens =
{
"", "-", "0", "1", "2", "3", "00", "01", "007", "+1", "-1", " 1", "1 ", "1a", "a1", "0x1", "1e1", "1.0",
"999999999", "1000000000", "99999999999999999", "999999999999999999", "1000000000000000000",
"9999999999999999999", "18446744073709551614", max_digits, "18446744073709551616",
"99999999999999999999999", "000000000000000000000"
};
for (const auto& token : tokens)
{
check_index_token(token);
}
}
SECTION("random tokens")
{
std::mt19937 gen(42);
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
for (int i = 0; i < 5000; ++i)
{
std::string token(static_cast<std::size_t>(gen() % 23), '0');
for (auto& c : token)
{
c = alphabet[gen() % alphabet.size()];
}
check_index_token(token);
}
}
}
TEST_CASE("JSON pointer operations on pointers with escaped tokens")
{
const std::vector<std::string> strings =
{
"", "/", "//", "/a", "/a/b", "/a~1b", "/~0", "/~01", "/~10", "/a~0~1/b/", "/0/1/2", "/ ", "/a/", "/~1/~0"
};
for (const auto& str : strings)
{
CAPTURE(str)
const json::json_pointer ptr(str);
CHECK(ptr.to_string() == str);
CHECK(ptr / "x" / "y" / std::size_t{3} == json::json_pointer(str + "/x/y/3"));
CHECK(ptr.parent_pointer() == (str.empty() ? ptr : json::json_pointer(str.substr(0, str.rfind('/')))));
// comparison with a string, in place, and with every other pointer
#if !defined(JSON_TEST_DEPRECATED_FUNCTIONS_DELETED)
for (const auto& other : strings)
{
CAPTURE(other)
CHECK((ptr == other) == (str == other));
CHECK((other == ptr) == (str == other));
CHECK((ptr != other) == (str != other));
}
CHECK_THROWS_AS(ptr == "x", json::parse_error&);
CHECK_THROWS_AS(ptr == "/~", json::parse_error&);
CHECK_THROWS_AS(ptr == "/b/~2", json::parse_error&);
#endif
}
}
TEST_CASE("JSON pointer lookups")
{
json j = {{"a", {{"b", {1, 2, 3}}}}, {"", 7}, {"~/", {{"x", nullptr}}}};
const json& cj = j;
CHECK(cj[json::json_pointer("/a/b/2")] == 3);
CHECK(cj.at(json::json_pointer("/a/b/1")) == 2);
CHECK(cj.at(json::json_pointer("/")) == 7);
CHECK(cj.contains(json::json_pointer("/~0~1/x")));
CHECK(!cj.contains(json::json_pointer("/a/c")));
CHECK(cj.value(json::json_pointer("/a/b/0"), 5) == 1);
CHECK(cj.value(json::json_pointer("/a/b/3"), 5) == 5);
CHECK(cj.value(json::json_pointer("/a/zz"), 5) == 5);
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/c")), json::out_of_range&);
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/b/3")), json::out_of_range&);
// non-const access reuses existing entries and creates missing ones
j[json::json_pointer("/a/b/1")] = 20;
j[json::json_pointer("/n/m")] = 1;
CHECK(j["a"]["b"] == json({1, 20, 3}));
CHECK(j["n"]["m"] == 1);
}
TEST_CASE("JSON patch with moves and copies")
{
json doc = {{"a", {{"b", {1, 2, 3}}}}, {"c", {{"d", "x"}}}};
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b","path":"/c/e"}])")) ==
json::parse(R"({"a":{},"c":{"d":"x","e":[1,2,3]}})"));
CHECK(doc.patch(json::parse(R"([{"op":"copy","from":"/a/b","path":"/c/e"}])")) ==
json::parse(R"({"a":{"b":[1,2,3]},"c":{"d":"x","e":[1,2,3]}})"));
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b/0","path":"/a/b/2"}])")) ==
json::parse(R"({"a":{"b":[2,3,1]},"c":{"d":"x"}})"));
// a failing move leaves a copy-patched document untouched
CHECK_THROWS_AS(doc.patch(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
json inplace = doc;
CHECK_THROWS_AS(inplace.patch_inplace(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
CHECK(inplace == doc);
}
-257
View File
@@ -1,257 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | 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
}