Compare commits

..
Author SHA1 Message Date
Niels Lohmann 89bd5d63e1 Rename names that Flawfinder takes for the file function open()
The comparison cursors' open() is now enter(), and a test's open and
close strings are head and tail.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 13:27:54 +02:00
Niels Lohmann 3fb7d9c710 Check the float tokens of a json_document image without a sort and a scan of every character
load(image_check::full) checked every float token of an image by sorting
the token ranges, scanning the token character by character, and comparing
the digit layout with the node's. Now:

- the ranges are sorted only if they are not sorted already (save() writes
  them in the order of the nodes, so they are);
- a token of the shape [-]digits.digits whose digit counts are the ones the
  node records (nearly all of them) is accepted by checking its digits eight
  at a time, without the general scan;
- the general scan skips runs of digits eight at a time.

Damaged tokens are still rejected as before: the check accepts exactly the
tokens that the general scan accepts.

Instructions of load(image_check::full) (develop -> now):

  macOS arm64        canada 44.6M -> 30.9M (-31 %), jeopardy 11.3M -> 10.7M,
                     citm 10.4M -> 10.0M, twitter 5.06M -> 4.94M
  Linux GCC 13       canada -50 %, jeopardy -43 %, twitter -31 %, citm -28 %
  Linux Clang 18     canada -42 %, jeopardy -48 %, twitter -35 %, citm -32 %

image_check::bounds is unchanged (within 0.4 %).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:30:44 +02:00
Niels Lohmann fa8134ae85 Compare json_view values without a vector per container and a basic_json per number
operator== of views (view/view, view/json, and editable documents) built two
vectors of members for every object it entered, sorted them with stable_sort
for every object, and converted each number to a basic_json value. It now:

- keeps the members of all open objects in one vector (reserved with
  geometric growth, so that nested objects do not reallocate), and orders
  them by insertion sort for up to 16 members, or not at all when the keys
  ascend already;
- compares numbers of one type directly, and builds a basic_json only for
  numbers of different types;
- compares short texts (keys, numbers, most strings) with two overlapping
  loads (bytes_equal(), in node.hpp);
- first walks both documents in lockstep, in the order of their text, which
  finds most pairs of equal values without the work of the comparison. The
  walk only enters an object if the comparison would visit its members in the
  same order (the object type keeps the order, the keys ascend, or all values
  are scalars), so a difference is never found later than before, and it
  records the containers it found different, so that the comparison does not
  walk them again;
- rejects values of different types and arrays of different sizes before any
  of this, and, for an object type that keeps the order, objects whose first
  (up to four) members have different keys, which ends a comparison that
  differs near the start without preparing the members.

Results are unchanged: numbers compare by value across types, duplicate keys
resolve as parse() resolves them, and objects compare in document order for
ordered_json and by key otherwise.

Instructions of one comparison of a document with an equal copy, macOS arm64
(develop -> now), json_document / ordered_json_document / editable:

  twitter    16.1M -> 7.0M   17.7M -> 1.5M    16.6M -> 7.3M
  citm       45.0M -> 3.4M   47.0M -> 3.2M    45.7M -> 4.3M
  canada    111.4M -> 13.7M  111.4M -> 13.7M  113.3M -> 15.0M
  jeopardy   26.2M -> 3.7M   27.3M -> 3.7M    26.6M -> 12.1M

and of a view with a basic_json value:

  twitter    11.4M -> 5.9M   11.2M -> 7.3M    11.6M -> 5.9M
  citm       33.9M -> 9.7M   32.6M -> 9.1M    34.6M -> 9.8M
  canada     92.9M -> 32.1M  92.9M -> 32.1M   93.9M -> 32.8M

and Linux instruction counts (GCC 13 / Clang 18) of the same comparisons:

  document with an equal copy   json_document     ordered_json_document   editable
    twitter                     -49 % / -52 %     -89 % / -89 %           -48 % / -50 %
    citm                        -90 % / -89 %     -91 % / -90 %           -88 % / -87 %
    jeopardy (4,000 questions)  -85 % / -85 %     -86 % / -86 %           -46 % / -49 %
  view with a basic_json value
    twitter                     -41 % / -45 %     -28 % / -35 %           -45 % / -44 %
    citm                        -63 % / -65 %     -63 % / -66 %           -65 % / -64 %
    jeopardy                    -39 % / -43 %     -14 % / -24 %           -44 % / -43 %

A sweep that places one difference at about 2,100 positions of eight
documents (a token at an even spacing, the first and last tokens, the first,
middle and last member of the root removed, one appended, the document
wrapped) in five kinds of comparison (view/view of read-only and of editable
documents, view/json, json_document, editable/view) finds the same results as
before and no placement slower on the Mac (Clang); on Linux no placement is
slower than 3 % (Clang and GCC).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:30:42 +02:00
10 changed files with 2642 additions and 1314 deletions

No files matched your search

+33 -232
View File
@@ -15,7 +15,7 @@
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <cstdlib> // strtoull
#include <cstring> // memchr, memcmp, memcpy
#include <cstring> // memcmp, memcpy
#ifndef JSON_NO_IO
#include <iosfwd> // ostream
#endif // JSON_NO_IO
@@ -303,12 +303,6 @@ class json_pointer
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
};
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
static bool is_dash(const string_t& s) noexcept
{
return s.size() == 1 && s[0] == '-';
}
/*!
@param[in] s reference token to be converted into an array index
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
@@ -334,7 +328,7 @@ class json_pointer
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
@@ -481,40 +475,9 @@ class json_pointer
return pos == size;
}
/// reserve room for @a n elements of an array type that has reserve()
template<typename ArrayType>
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
-> decltype(array.reserve(n), void())
{
array.reserve(n);
}
template<typename ArrayType>
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
{}
/// @brief object member access with a key that is only copied if it is inserted
///
/// Objects that can find the position of a key and insert there
/// (std::map) search once, others look the key up and add it if it is missing.
template<typename BasicJsonType>
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
{
auto& members = *object.m_data.m_value.object;
auto it = members.lower_bound(key);
if (it != members.end() && !members.key_comp()(key, it->first))
{
return it->second;
}
const auto old_capacity = object.container_capacity();
it = members.emplace_hint(it, key, nullptr);
return object.set_parent_after_insert(it->second, old_capacity);
}
template<typename BasicJsonType>
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
auto& members = *object.m_data.m_value.object;
const auto it = members.find(key);
@@ -525,12 +488,6 @@ class json_pointer
return object.operator[](key);
}
template<typename BasicJsonType>
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
return object_member_impl(object, key, detail::priority_tag<1> {});
}
JSON_PRIVATE_UNLESS_TESTED:
json_pointer top() const
{
@@ -763,103 +720,48 @@ class json_pointer
}
}
/*!
@brief the values along the pointer last resolved by @ref resolve_plain
A patch from @ref basic_json::diff lists its operations in document order,
so consecutive pointers share most of their reference tokens; the values
along the previous pointer are kept to resume from the common prefix.
`steps[k]` is the value after the first `k` reference tokens together with
the length of the prefix of `*path` that holds them. A value stays valid as
long as no operation changes its container: callers keep only values up to
the parent of the pointer they modify, or call `reset()`.
*/
template<typename BasicJsonType>
struct plain_steps
{
const string_t* path = nullptr;
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
void reset() noexcept
{
path = nullptr;
steps.clear();
}
};
/*!
@brief resolve a plain JSON pointer without building its reference tokens
@param[in] root the value the pointer starts at
@param[in] ptr the value the pointer starts at
@param[in] path a JSON pointer string
@param[in] length the number of leading bytes of @a path to resolve; 0 or
the position of a slash
@param[in,out] token a string to hold the reference tokens, to reuse its storage
@param[in,out] cache the values along the previously resolved pointer;
@a path must outlive the next call
@return the pointed to value, or `nullptr` if the pointer does not denote
an existing value, or does so in a way that is not plain: an array
index that is malformed or out of range, or a "-", or a reference
token with an escape. No exception is
thrown for these; callers use @ref json_pointer instead, which
handles them.
an existing value, or does so in a way that is not plain: a
reference token with an escape, an array index that is malformed or
out of range, or a "-". No exception is thrown for these; callers
use @ref json_pointer instead, which handles them.
*/
template<typename BasicJsonType>
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
{
auto& steps = cache.steps;
// resume from the deepest step that is also a prefix of this path
std::size_t depth = 1;
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
{
const string_t& previous = *cache.path;
for (depth = steps.size(); depth > 1; --depth)
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
{
break;
}
}
steps.resize(depth);
}
else
{
steps.assign(1, std::make_pair(std::size_t{0}, root));
}
cache.path = &path;
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
for (std::size_t pos = 0; pos < length;)
{
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
{
return nullptr;
}
// the reference token between the slash at pos and the next one,
// which must not have an escape
const char* const begin = path.data() + pos + 1;
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
// the reference token between the slash at pos and the next one
std::size_t end = path.find_first_of('/', pos + 1);
if (end == string_t::npos || end > length)
{
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
{
return nullptr;
}
++end;
end = length;
}
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
{
return nullptr;
}
switch (ptr->type())
{
case detail::value_t::object:
{
token.clear();
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{
@@ -871,24 +773,8 @@ class json_pointer
case detail::value_t::array:
{
// an index of up to 18 digits without a leading zero fits into size_type
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
const std::size_t digits = static_cast<std::size_t>(end - begin);
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
{
return nullptr;
}
typename BasicJsonType::size_type idx = 0;
for (const char* c = begin; c != end; ++c)
{
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
{
return nullptr;
}
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
typename BasicJsonType::size_type idx{};
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
@@ -907,8 +793,7 @@ class json_pointer
default:
return nullptr;
}
pos = static_cast<std::size_t>(end - path.data());
steps.emplace_back(pos, ptr);
pos = end;
}
return ptr;
@@ -1022,7 +907,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
@@ -1093,7 +978,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" cannot be used for const access
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
@@ -1155,7 +1040,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return nullptr;
@@ -1232,7 +1117,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return false;
@@ -1391,52 +1276,6 @@ class json_pointer
obj[key] = std::forward<ValueType>(v);
}
/// the number of entries that flattening @a value adds: its primitive
/// values and empty containers
template<typename BasicJsonType>
static std::size_t count_flattened(const BasicJsonType& value)
{
std::size_t count = 0;
std::vector<const BasicJsonType*> pending(1, &value);
while (!pending.empty())
{
const BasicJsonType* const v = pending.back();
pending.pop_back();
if (v->is_array() && !v->m_data.m_value.array->empty())
{
for (const auto& element : *v->m_data.m_value.array)
{
pending.push_back(&element);
}
}
else if (v->is_object() && !v->m_data.m_value.object->empty())
{
for (const auto& member : *v->m_data.m_value.object)
{
pending.push_back(&member.second);
}
}
else
{
++count;
}
}
return count;
}
// Reserve the storage for @a value of objects that have a reserve(), so
// that it does not have to be moved while it grows
template<typename ObjectType, typename BasicJsonType>
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
-> decltype(obj.reserve(std::size_t {}), void())
{
obj.reserve(count_flattened(value));
}
template<typename ObjectType, typename BasicJsonType>
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
{}
/// append the decimal representation of @a i to @a out
static void append_index(string_t& out, std::size_t i)
{
@@ -1546,7 +1385,6 @@ class json_pointer
}
};
reserve_flattened(object, value, detail::priority_tag<1> {});
enter(value);
while (!stack.empty())
{
@@ -1554,9 +1392,8 @@ class json_pointer
// frame, which would invalidate a reference to it
const BasicJsonType* const container = stack.back().container;
// the path of the previous child is replaced by the path of the
// next one; for an array element in one resize
const std::size_t path_length = stack.back().path_length;
// drop the path of the previous child
path.resize(stack.back().path_length);
if (container->is_array())
{
@@ -1570,20 +1407,8 @@ class json_pointer
// iterate array and use index as a reference string
++stack.back().index;
std::array<char, 24> digits{};
std::size_t pos = digits.size();
std::size_t rest = i;
do
{
digits[--pos] = static_cast<char>('0' + rest % 10);
rest /= 10;
}
while (rest != 0);
const std::size_t count = digits.size() - pos;
path.resize(path_length + 1 + count);
char* const out = &path[path_length];
out[0] = '/';
std::memcpy(out + 1, digits.data() + pos, count);
path.push_back('/');
append_index(path, i);
enter(array[i]);
}
else
@@ -1597,7 +1422,6 @@ class json_pointer
// iterate object and use keys as reference string
++stack.back().member;
path.resize(path_length);
path.push_back('/');
detail::escape_into(path, it->first);
enter(it->second);
@@ -1693,28 +1517,8 @@ class json_pointer
{
end = key.size();
}
const std::size_t token_size = end - pos - 1;
// a token that is a plain index of up to 9 digits, which is
// the case for almost all array elements, is not copied
std::size_t index = 0;
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
{
plain_index = data[k] >= '0' && data[k] <= '9';
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
}
if (plain_index)
{
current = &current->operator[](static_cast<typename BasicJsonType::size_type>(index));
values.push_back(current);
ends.push_back(end);
pos = end;
continue;
}
token.clear();
token.append(data + pos + 1, token_size);
token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos)
{
detail::unescape(token);
@@ -1724,13 +1528,10 @@ class json_pointer
{
case detail::value_t::null:
{
// a first child 0 makes the value an array without a look-up
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
if (arrays.contains(data, pos))
{
// some reference token below this position is 0, so the
// value is an array
*current = BasicJsonType(detail::value_t::array);
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
current = &current->operator[](array_index<BasicJsonType>(token));
}
else
File diff suppressed because it is too large. Load diff
+101 -18
View File
@@ -255,6 +255,82 @@ struct number_token
bool is_float; ///< a fraction or an exponent
};
/// advance over ASCII digits, eight bytes at a time
inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
while (e - p >= 8)
{
// (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry
// out of a byte with bit 7 set only affects the bytes above, after the first one
// that count_trailing_zeros finds.)
const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u);
const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u);
if (not_digit != 0)
{
return p + (static_cast<std::size_t>(count_trailing_zeros(not_digit)) / 8);
}
p += 8;
}
while (p != e && is_digit(*p))
{
++p;
}
return p;
}
/// whether the n bytes at p are ASCII digits (n > 0)
inline bool all_digits(const unsigned char* p, std::size_t n) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
const auto digits = [](const unsigned char* q)
{
const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u);
return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0;
};
if (n >= 8)
{
std::size_t i = 0;
for (; i + 8 < n; i += 8)
{
if (!digits(p + i))
{
return false;
}
}
return digits(p + n - 8); // (overlapping the bytes before: they are digits)
}
for (std::size_t i = 0; i < n; ++i)
{
if (!is_digit(p[i]))
{
return false;
}
}
return true;
}
/// Whether a float token has the shape that the parser records for most of
/// them: [-]digits.digits, with the numbers of digits of the node's layout
/// (extra). Then the token is a number of that layout, and the general scan
/// that follows is not needed.
inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept
{
const std::size_t int_digits = extra & 0xFFu;
const std::size_t frac_digits = extra >> 8u;
if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0)
{
return false;
}
const std::size_t negative = *s == '-' ? 1 : 0;
if (len != negative + int_digits + 1 + frac_digits)
{
return false;
}
const unsigned char* const p = s + negative;
return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits);
}
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
{
@@ -273,11 +349,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
}
else if (*p >= '1' && *p <= '9')
{
do
{
++p;
}
while (p != e && is_digit(*p));
p = digit_run(p + 1, e);
}
else
{
@@ -289,10 +361,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
if (p != e && *p == '.')
{
const unsigned char* const f0 = ++p;
while (p != e && is_digit(*p))
{
++p;
}
p = digit_run(p, e);
if (p == f0)
{
return false;
@@ -403,10 +472,15 @@ struct float_range
template<typename Check>
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
{
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
// (save() writes the ranges in the order of the nodes, which is sorted already)
const auto before = [](const byte_range & a, const byte_range & b)
{
return a.off != b.off ? a.off < b.off : a.len < b.len;
});
};
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
{
std::sort(ranges.begin(), ranges.end(), before);
}
std::size_t end = 0;
for (std::size_t i = 0; i < ranges.size(); ++i)
{
@@ -428,7 +502,8 @@ bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
/// record layouts that match it), tokens must not overlap.
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
{
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
// (save() writes the tokens in the order of the nodes, which is sorted already)
const auto before = [](const float_range & a, const float_range & b)
{
if (a.off != b.off)
{
@@ -439,7 +514,11 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
return a.len < b.len;
}
return a.extra < b.extra;
});
};
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
{
std::sort(ranges.begin(), ranges.end(), before);
}
std::size_t end = 0;
std::size_t i = 0;
while (i < ranges.size())
@@ -449,14 +528,18 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
{
return false;
}
number_token t{};
const unsigned char* const s = text + r.off;
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
std::uint16_t layout = r.extra;
if (!plain_float_token(s, r.len, r.extra))
{
return false;
number_token t{};
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
{
return false;
}
// the digit layout the parser records (or "many", as compaction writes it)
layout = float_layout(t);
}
// the digit layout the parser records (or "many", as compaction writes it)
const std::uint16_t layout = float_layout(t);
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
{
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
+68
View File
@@ -120,6 +120,74 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u);
}
/// whether n bytes are equal; the short texts of JSON (keys, numbers, most
/// strings) are compared by two overlapping loads instead of a library call
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_equal(const char* x, const char* y, std::size_t n) noexcept
{
const auto load8 = [](const char* p)
{
std::uint64_t v = 0;
std::memcpy(&v, p, sizeof(v));
return v;
};
const auto load4 = [](const char* p)
{
std::uint32_t v = 0;
std::memcpy(&v, p, sizeof(v));
return v;
};
if (n >= 8)
{
std::size_t i = 0;
for (; i + 8 < n; i += 8)
{
if (load8(x + i) != load8(y + i))
{
return false;
}
}
return load8(x + n - 8) == load8(y + n - 8);
}
if (n >= 4)
{
return load4(x) == load4(y) && load4(x + n - 4) == load4(y + n - 4);
}
for (std::size_t i = 0; i < n; ++i)
{
if (x[i] != y[i])
{
return false;
}
}
return true;
}
/// whether the n1 bytes at x are before the n2 bytes at y (memcmp() order, then the shorter first);
/// the first difference is found eight bytes at a time
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_less(const char* x, std::size_t n1, const char* y, std::size_t n2) noexcept
{
const std::size_t n = n1 < n2 ? n1 : n2;
std::size_t i = 0;
for (; i + 8 <= n; i += 8)
{
const std::uint64_t u = read_eight_bytes(x + i);
const std::uint64_t v = read_eight_bytes(y + i);
if (u != v)
{
const auto shift = static_cast<unsigned>(count_trailing_zeros(u ^ v)) & ~7u; // (the first byte that differs)
return ((u >> shift) & 0xFFu) < ((v >> shift) & 0xFFu);
}
}
for (; i < n; ++i)
{
if (x[i] != y[i])
{
return static_cast<unsigned char>(x[i]) < static_cast<unsigned char>(y[i]);
}
}
return n1 < n2;
}
/// estimated number of nodes for an input of `size` bytes (one node per ~12
/// bytes covers typical documents without regrowth)
inline std::size_t estimate_nodes(std::size_t size) noexcept
+32 -157
View File
@@ -6878,72 +6878,6 @@ public:
/// @name JSON Patch functions
/// @{
private:
/// the members of an operation of a JSON patch that are of interest
struct patch_members
{
basic_json* op = nullptr;
basic_json* path = nullptr;
basic_json* value = nullptr;
basic_json* from = nullptr;
};
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
{
patch_members members;
for (auto& member : *val.m_data.m_value.object)
{
const string_t& key = member.first;
switch (key.size())
{
case 2:
if (key == "op")
{
members.op = &member.second;
}
break;
case 4:
if (key == "path")
{
members.path = &member.second;
}
else if (key == "from")
{
members.from = &member.second;
}
break;
case 5:
if (key == "value")
{
members.value = &member.second;
}
break;
default:
break;
}
}
return members;
}
/// @brief find the members by lookup for key types other than string_t
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
{
const auto find_member = [&val](const char* name) -> basic_json *
{
const auto it = val.m_data.m_value.object->find(string_t(name));
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
};
patch_members members;
members.op = find_member("op");
members.path = find_member("path");
members.value = find_member("value");
members.from = find_member("from");
return members;
}
public:
/// @brief applies a JSON patch in-place without copying the object
/// @sa https://json.nlohmann.me/api/basic_json/patch/
void patch_inplace(const basic_json& json_patch)
@@ -7117,18 +7051,18 @@ public:
// a diff creates them: apply the operation and return true; return
// false, with nothing changed, for all others
string_t token;
typename json_pointer::template plain_steps<basic_json> steps;
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
const string_t value_key = "value";
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
{
if (path.empty())
if (path.empty() || path.find_first_of('~') != string_t::npos)
{
return false;
}
// a missing "value" is reported below
const basic_json* const value_ptr = members.value;
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
if (operation != patch_operations::remove && value_ptr == nullptr)
const auto value_it = val.m_data.m_value.object->find(value_key);
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
{
return false;
}
@@ -7137,7 +7071,7 @@ public:
{
case patch_operations::replace:
{
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr)
{
return false;
@@ -7148,7 +7082,7 @@ public:
case patch_operations::test:
{
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
// a failing test is reported below
return target != nullptr && *target == value;
}
@@ -7168,17 +7102,13 @@ public:
}
--slash;
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
if (parent == nullptr)
{
return false;
}
token.clear();
token.append(path.data() + slash + 1, path.size() - slash - 1);
if (token.find_first_of('~') != string_t::npos)
{
return false;
}
if (operation == patch_operations::add)
{
@@ -7234,32 +7164,31 @@ public:
}
};
// the name of the first member to check
const string_t op_member = "op";
// iterate and apply the operations
for (const auto& val : json_patch)
{
// wrapper to get a value for an operation
const auto get_value = [&val](basic_json * found,
const string_t& op,
const char* member,
const auto get_value = [&val](const string_t& op,
const string_t& member,
bool string_type) -> basic_json &
{
// find value
auto it = val.m_data.m_value.object->find(member);
// check if the desired value is present
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{
detail::throw_patch_missing_member(op, string_t(member), false, val);
detail::throw_patch_missing_member(op, member, false, val);
}
// check if the result is of type string
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{
detail::throw_patch_missing_member(op, string_t(member), true, val);
detail::throw_patch_missing_member(op, member, true, val);
}
// no error: return value
return *found;
return it->second;
};
// type check: every element of the array must be an object
@@ -7268,30 +7197,26 @@ public:
detail::throw_patch_not_an_array(val);
}
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
// collect mandatory members
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
const patch_operations operation = get_op(op);
// the usual operations on a plain pointer are applied without
// building the pointer's reference tokens; anything else (and
// every error) is handled below
if (try_plain(operation, path, members, val))
if (try_plain(operation, path, val))
{
continue;
}
// the general operations may change any container
steps.reset();
json_pointer ptr(path);
switch (operation)
{
case patch_operations::add:
{
operation_add(ptr, get_value(members.value, "add", "value", false));
operation_add(ptr, get_value("add", "value", false));
break;
}
@@ -7304,13 +7229,13 @@ public:
case patch_operations::replace:
{
// the "path" location must exist - use at()
result.at(ptr) = get_value(members.value, "replace", "value", false);
result.at(ptr) = get_value("replace", "value", false);
break;
}
case patch_operations::move:
{
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
@@ -7333,7 +7258,7 @@ public:
case patch_operations::copy:
{
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
const json_pointer from_ptr(from_path);
// The copy is functionally identical to an "add"
@@ -7351,7 +7276,7 @@ public:
{
// check if "value" matches the one at "path"
// the "path" location must exist - use at()
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
success = (result.at(ptr) == get_value("test", "value", false));
}
JSON_INTERNAL_CATCH (out_of_range&)
{
@@ -7443,18 +7368,13 @@ public:
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
{
// the keys are distinct and in the order of std::map; the members are
// added without looking them up
basic_json entry(value_t::object);
auto& members = *entry.m_data.m_value.object;
typename object_t::iterator hint = members.end();
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
entry["op"] = op;
entry["path"] = path;
if (value != nullptr)
{
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
entry["value"] = *value;
}
entry.set_parents();
result.push_back(std::move(entry));
}
@@ -7476,50 +7396,6 @@ public:
diff_operation(result, "add", path, &value);
}
/// @brief the values in the target of the members of a source object, for the
/// objects of up to 16 members without allocating
///
/// reserve() must be called with the number of values that are added.
class diff_target_values
{
public:
diff_target_values() = default;
diff_target_values(const diff_target_values&) = delete;
diff_target_values& operator=(const diff_target_values&) = delete;
~diff_target_values() = default;
void reserve(const std::size_t n)
{
if (n > inline_size)
{
large.resize(n);
items = large.data();
}
}
void push_back(const basic_json* value)
{
items[count++] = value;
}
std::size_t size() const noexcept
{
return count;
}
const basic_json* operator[](const std::size_t i) const noexcept
{
return items[i];
}
private:
static constexpr std::size_t inline_size = 16;
std::size_t count = 0;
const basic_json** items = inline_items;
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
const basic_json* inline_items[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
};
/// @brief append "/" and the decimal representation of @a index to @a path
static void diff_append_index(string_t& path, std::size_t index)
{
@@ -7600,9 +7476,8 @@ public:
operations that remove every member of @a source and add every member of
@a target to @a result, and returns false.
*/
template<typename TargetValues>
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, TargetValues& target_values,
const string_t& path, std::vector<const basic_json*>& target_values,
std::vector<const_iterator>& added)
{
// first pass: one lookup per source key
@@ -7816,7 +7691,7 @@ public:
case value_t::object:
{
diff_target_values target_values;
std::vector<const basic_json*> target_values;
std::vector<const_iterator> added;
if (diff_object_keys(result, source, target, path, target_values, added))
{
+4
View File
@@ -723,8 +723,12 @@ class basic_json_view
template<typename, bool> friend class basic_json_document;
template<typename, bool> friend class basic_json_view;
template<typename, typename> friend class detail::view::editor;
template<typename, typename> friend class detail::view::view_side;
friend iterator;
/// whether the view is one of an editable document
static constexpr bool is_editable = Editable;
basic_json_view(const document_data* d, const node* n) noexcept
: m_doc(d), m_node(n)
{}
+65 -389
View File
@@ -22357,7 +22357,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <cstdlib> // strtoull
#include <cstring> // memchr, memcmp, memcpy
#include <cstring> // memcmp, memcpy
#ifndef JSON_NO_IO
#include <iosfwd> // ostream
#endif // JSON_NO_IO
@@ -22653,12 +22653,6 @@ class json_pointer
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
};
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
static bool is_dash(const string_t& s) noexcept
{
return s.size() == 1 && s[0] == '-';
}
/*!
@param[in] s reference token to be converted into an array index
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
@@ -22684,7 +22678,7 @@ class json_pointer
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
@@ -22831,40 +22825,9 @@ class json_pointer
return pos == size;
}
/// reserve room for @a n elements of an array type that has reserve()
template<typename ArrayType>
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
-> decltype(array.reserve(n), void())
{
array.reserve(n);
}
template<typename ArrayType>
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
{}
/// @brief object member access with a key that is only copied if it is inserted
///
/// Objects that can find the position of a key and insert there
/// (std::map) search once, others look the key up and add it if it is missing.
template<typename BasicJsonType>
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
{
auto& members = *object.m_data.m_value.object;
auto it = members.lower_bound(key);
if (it != members.end() && !members.key_comp()(key, it->first))
{
return it->second;
}
const auto old_capacity = object.container_capacity();
it = members.emplace_hint(it, key, nullptr);
return object.set_parent_after_insert(it->second, old_capacity);
}
template<typename BasicJsonType>
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
auto& members = *object.m_data.m_value.object;
const auto it = members.find(key);
@@ -22875,12 +22838,6 @@ class json_pointer
return object.operator[](key);
}
template<typename BasicJsonType>
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
return object_member_impl(object, key, detail::priority_tag<1> {});
}
JSON_PRIVATE_UNLESS_TESTED:
json_pointer top() const
{
@@ -23113,103 +23070,48 @@ class json_pointer
}
}
/*!
@brief the values along the pointer last resolved by @ref resolve_plain
A patch from @ref basic_json::diff lists its operations in document order,
so consecutive pointers share most of their reference tokens; the values
along the previous pointer are kept to resume from the common prefix.
`steps[k]` is the value after the first `k` reference tokens together with
the length of the prefix of `*path` that holds them. A value stays valid as
long as no operation changes its container: callers keep only values up to
the parent of the pointer they modify, or call `reset()`.
*/
template<typename BasicJsonType>
struct plain_steps
{
const string_t* path = nullptr;
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
void reset() noexcept
{
path = nullptr;
steps.clear();
}
};
/*!
@brief resolve a plain JSON pointer without building its reference tokens
@param[in] root the value the pointer starts at
@param[in] ptr the value the pointer starts at
@param[in] path a JSON pointer string
@param[in] length the number of leading bytes of @a path to resolve; 0 or
the position of a slash
@param[in,out] token a string to hold the reference tokens, to reuse its storage
@param[in,out] cache the values along the previously resolved pointer;
@a path must outlive the next call
@return the pointed to value, or `nullptr` if the pointer does not denote
an existing value, or does so in a way that is not plain: an array
index that is malformed or out of range, or a "-", or a reference
token with an escape. No exception is
thrown for these; callers use @ref json_pointer instead, which
handles them.
an existing value, or does so in a way that is not plain: a
reference token with an escape, an array index that is malformed or
out of range, or a "-". No exception is thrown for these; callers
use @ref json_pointer instead, which handles them.
*/
template<typename BasicJsonType>
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
{
auto& steps = cache.steps;
// resume from the deepest step that is also a prefix of this path
std::size_t depth = 1;
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
{
const string_t& previous = *cache.path;
for (depth = steps.size(); depth > 1; --depth)
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
{
break;
}
}
steps.resize(depth);
}
else
{
steps.assign(1, std::make_pair(std::size_t{0}, root));
}
cache.path = &path;
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
for (std::size_t pos = 0; pos < length;)
{
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
{
return nullptr;
}
// the reference token between the slash at pos and the next one,
// which must not have an escape
const char* const begin = path.data() + pos + 1;
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
// the reference token between the slash at pos and the next one
std::size_t end = path.find_first_of('/', pos + 1);
if (end == string_t::npos || end > length)
{
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
{
return nullptr;
}
++end;
end = length;
}
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
{
return nullptr;
}
switch (ptr->type())
{
case detail::value_t::object:
{
token.clear();
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{
@@ -23221,24 +23123,8 @@ class json_pointer
case detail::value_t::array:
{
// an index of up to 18 digits without a leading zero fits into size_type
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
const std::size_t digits = static_cast<std::size_t>(end - begin);
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
{
return nullptr;
}
typename BasicJsonType::size_type idx = 0;
for (const char* c = begin; c != end; ++c)
{
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
{
return nullptr;
}
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
typename BasicJsonType::size_type idx{};
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
@@ -23257,8 +23143,7 @@ class json_pointer
default:
return nullptr;
}
pos = static_cast<std::size_t>(end - path.data());
steps.emplace_back(pos, ptr);
pos = end;
}
return ptr;
@@ -23372,7 +23257,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
@@ -23443,7 +23328,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" cannot be used for const access
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
@@ -23505,7 +23390,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return nullptr;
@@ -23582,7 +23467,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return false;
@@ -23741,52 +23626,6 @@ class json_pointer
obj[key] = std::forward<ValueType>(v);
}
/// the number of entries that flattening @a value adds: its primitive
/// values and empty containers
template<typename BasicJsonType>
static std::size_t count_flattened(const BasicJsonType& value)
{
std::size_t count = 0;
std::vector<const BasicJsonType*> pending(1, &value);
while (!pending.empty())
{
const BasicJsonType* const v = pending.back();
pending.pop_back();
if (v->is_array() && !v->m_data.m_value.array->empty())
{
for (const auto& element : *v->m_data.m_value.array)
{
pending.push_back(&element);
}
}
else if (v->is_object() && !v->m_data.m_value.object->empty())
{
for (const auto& member : *v->m_data.m_value.object)
{
pending.push_back(&member.second);
}
}
else
{
++count;
}
}
return count;
}
// Reserve the storage for @a value of objects that have a reserve(), so
// that it does not have to be moved while it grows
template<typename ObjectType, typename BasicJsonType>
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
-> decltype(obj.reserve(std::size_t {}), void())
{
obj.reserve(count_flattened(value));
}
template<typename ObjectType, typename BasicJsonType>
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
{}
/// append the decimal representation of @a i to @a out
static void append_index(string_t& out, std::size_t i)
{
@@ -23896,7 +23735,6 @@ class json_pointer
}
};
reserve_flattened(object, value, detail::priority_tag<1> {});
enter(value);
while (!stack.empty())
{
@@ -23904,9 +23742,8 @@ class json_pointer
// frame, which would invalidate a reference to it
const BasicJsonType* const container = stack.back().container;
// the path of the previous child is replaced by the path of the
// next one; for an array element in one resize
const std::size_t path_length = stack.back().path_length;
// drop the path of the previous child
path.resize(stack.back().path_length);
if (container->is_array())
{
@@ -23920,20 +23757,8 @@ class json_pointer
// iterate array and use index as a reference string
++stack.back().index;
std::array<char, 24> digits{};
std::size_t pos = digits.size();
std::size_t rest = i;
do
{
digits[--pos] = static_cast<char>('0' + rest % 10);
rest /= 10;
}
while (rest != 0);
const std::size_t count = digits.size() - pos;
path.resize(path_length + 1 + count);
char* const out = &path[path_length];
out[0] = '/';
std::memcpy(out + 1, digits.data() + pos, count);
path.push_back('/');
append_index(path, i);
enter(array[i]);
}
else
@@ -23947,7 +23772,6 @@ class json_pointer
// iterate object and use keys as reference string
++stack.back().member;
path.resize(path_length);
path.push_back('/');
detail::escape_into(path, it->first);
enter(it->second);
@@ -24043,28 +23867,8 @@ class json_pointer
{
end = key.size();
}
const std::size_t token_size = end - pos - 1;
// a token that is a plain index of up to 9 digits, which is
// the case for almost all array elements, is not copied
std::size_t index = 0;
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
{
plain_index = data[k] >= '0' && data[k] <= '9';
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
}
if (plain_index)
{
current = &current->operator[](static_cast<typename BasicJsonType::size_type>(index));
values.push_back(current);
ends.push_back(end);
pos = end;
continue;
}
token.clear();
token.append(data + pos + 1, token_size);
token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos)
{
detail::unescape(token);
@@ -24074,13 +23878,10 @@ class json_pointer
{
case detail::value_t::null:
{
// a first child 0 makes the value an array without a look-up
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
if (arrays.contains(data, pos))
{
// some reference token below this position is 0, so the
// value is an array
*current = BasicJsonType(detail::value_t::array);
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
current = &current->operator[](array_index<BasicJsonType>(token));
}
else
@@ -39387,72 +39188,6 @@ public:
/// @name JSON Patch functions
/// @{
private:
/// the members of an operation of a JSON patch that are of interest
struct patch_members
{
basic_json* op = nullptr;
basic_json* path = nullptr;
basic_json* value = nullptr;
basic_json* from = nullptr;
};
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
{
patch_members members;
for (auto& member : *val.m_data.m_value.object)
{
const string_t& key = member.first;
switch (key.size())
{
case 2:
if (key == "op")
{
members.op = &member.second;
}
break;
case 4:
if (key == "path")
{
members.path = &member.second;
}
else if (key == "from")
{
members.from = &member.second;
}
break;
case 5:
if (key == "value")
{
members.value = &member.second;
}
break;
default:
break;
}
}
return members;
}
/// @brief find the members by lookup for key types other than string_t
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
{
const auto find_member = [&val](const char* name) -> basic_json *
{
const auto it = val.m_data.m_value.object->find(string_t(name));
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
};
patch_members members;
members.op = find_member("op");
members.path = find_member("path");
members.value = find_member("value");
members.from = find_member("from");
return members;
}
public:
/// @brief applies a JSON patch in-place without copying the object
/// @sa https://json.nlohmann.me/api/basic_json/patch/
void patch_inplace(const basic_json& json_patch)
@@ -39626,18 +39361,18 @@ public:
// a diff creates them: apply the operation and return true; return
// false, with nothing changed, for all others
string_t token;
typename json_pointer::template plain_steps<basic_json> steps;
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
const string_t value_key = "value";
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
{
if (path.empty())
if (path.empty() || path.find_first_of('~') != string_t::npos)
{
return false;
}
// a missing "value" is reported below
const basic_json* const value_ptr = members.value;
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
if (operation != patch_operations::remove && value_ptr == nullptr)
const auto value_it = val.m_data.m_value.object->find(value_key);
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
{
return false;
}
@@ -39646,7 +39381,7 @@ public:
{
case patch_operations::replace:
{
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr)
{
return false;
@@ -39657,7 +39392,7 @@ public:
case patch_operations::test:
{
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
// a failing test is reported below
return target != nullptr && *target == value;
}
@@ -39677,17 +39412,13 @@ public:
}
--slash;
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
if (parent == nullptr)
{
return false;
}
token.clear();
token.append(path.data() + slash + 1, path.size() - slash - 1);
if (token.find_first_of('~') != string_t::npos)
{
return false;
}
if (operation == patch_operations::add)
{
@@ -39743,32 +39474,31 @@ public:
}
};
// the name of the first member to check
const string_t op_member = "op";
// iterate and apply the operations
for (const auto& val : json_patch)
{
// wrapper to get a value for an operation
const auto get_value = [&val](basic_json * found,
const string_t& op,
const char* member,
const auto get_value = [&val](const string_t& op,
const string_t& member,
bool string_type) -> basic_json &
{
// find value
auto it = val.m_data.m_value.object->find(member);
// check if the desired value is present
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{
detail::throw_patch_missing_member(op, string_t(member), false, val);
detail::throw_patch_missing_member(op, member, false, val);
}
// check if the result is of type string
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{
detail::throw_patch_missing_member(op, string_t(member), true, val);
detail::throw_patch_missing_member(op, member, true, val);
}
// no error: return value
return *found;
return it->second;
};
// type check: every element of the array must be an object
@@ -39777,30 +39507,26 @@ public:
detail::throw_patch_not_an_array(val);
}
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
// collect mandatory members
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
const patch_operations operation = get_op(op);
// the usual operations on a plain pointer are applied without
// building the pointer's reference tokens; anything else (and
// every error) is handled below
if (try_plain(operation, path, members, val))
if (try_plain(operation, path, val))
{
continue;
}
// the general operations may change any container
steps.reset();
json_pointer ptr(path);
switch (operation)
{
case patch_operations::add:
{
operation_add(ptr, get_value(members.value, "add", "value", false));
operation_add(ptr, get_value("add", "value", false));
break;
}
@@ -39813,13 +39539,13 @@ public:
case patch_operations::replace:
{
// the "path" location must exist - use at()
result.at(ptr) = get_value(members.value, "replace", "value", false);
result.at(ptr) = get_value("replace", "value", false);
break;
}
case patch_operations::move:
{
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
@@ -39842,7 +39568,7 @@ public:
case patch_operations::copy:
{
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
const json_pointer from_ptr(from_path);
// The copy is functionally identical to an "add"
@@ -39860,7 +39586,7 @@ public:
{
// check if "value" matches the one at "path"
// the "path" location must exist - use at()
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
success = (result.at(ptr) == get_value("test", "value", false));
}
JSON_INTERNAL_CATCH (out_of_range&)
{
@@ -39952,18 +39678,13 @@ public:
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
{
// the keys are distinct and in the order of std::map; the members are
// added without looking them up
basic_json entry(value_t::object);
auto& members = *entry.m_data.m_value.object;
typename object_t::iterator hint = members.end();
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
entry["op"] = op;
entry["path"] = path;
if (value != nullptr)
{
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
entry["value"] = *value;
}
entry.set_parents();
result.push_back(std::move(entry));
}
@@ -39985,50 +39706,6 @@ public:
diff_operation(result, "add", path, &value);
}
/// @brief the values in the target of the members of a source object, for the
/// objects of up to 16 members without allocating
///
/// reserve() must be called with the number of values that are added.
class diff_target_values
{
public:
diff_target_values() = default;
diff_target_values(const diff_target_values&) = delete;
diff_target_values& operator=(const diff_target_values&) = delete;
~diff_target_values() = default;
void reserve(const std::size_t n)
{
if (n > inline_size)
{
large.resize(n);
items = large.data();
}
}
void push_back(const basic_json* value)
{
items[count++] = value;
}
std::size_t size() const noexcept
{
return count;
}
const basic_json* operator[](const std::size_t i) const noexcept
{
return items[i];
}
private:
static constexpr std::size_t inline_size = 16;
std::size_t count = 0;
const basic_json** items = inline_items;
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
const basic_json* inline_items[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
};
/// @brief append "/" and the decimal representation of @a index to @a path
static void diff_append_index(string_t& path, std::size_t index)
{
@@ -40109,9 +39786,8 @@ public:
operations that remove every member of @a source and add every member of
@a target to @a result, and returns false.
*/
template<typename TargetValues>
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, TargetValues& target_values,
const string_t& path, std::vector<const basic_json*>& target_values,
std::vector<const_iterator>& added)
{
// first pass: one lookup per source key
@@ -40325,7 +40001,7 @@ public:
case value_t::object:
{
diff_target_values target_values;
std::vector<const basic_json*> target_values;
std::vector<const_iterator> added;
if (diff_object_keys(result, source, target, path, target_values, added))
{
+1323 -438
View File
File diff suppressed because it is too large. Load diff
+150
View File
@@ -345,6 +345,33 @@ TEST_CASE("json_view comparison")
}
}
SECTION("objects whose first keys differ or repeat")
{
const char* const texts[] =
{
R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})",
R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})",
R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})",
R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])"
};
for (const char* const x : texts)
{
for (const char* const y : texts)
{
CAPTURE(x)
CAPTURE(y)
const ordered_json_document dx = ordered_json_document::parse(x);
const ordered_json_document dy = ordered_json_document::parse(y);
const ordered_json_editable_document ex = ordered_json_editable_document::parse(x);
const ordered_json jx = ordered_json::parse(x);
const ordered_json jy = ordered_json::parse(y);
CHECK((dx.root() == dy.root()) == (jx == jy));
CHECK((dx.root() == jy) == (jx == jy));
CHECK((ex.root() == dy.root()) == (jx == jy));
}
}
}
SECTION("numbers, duplicate keys, member order")
{
const auto same = [](const char* x, const char* y)
@@ -385,6 +412,129 @@ TEST_CASE("json_view comparison")
CHECK(!(null_document.root() == discarded));
}
SECTION("values that are the same text, or the same value written differently, at any depth")
{
// nested arrays and objects around depths where the walk over equal text gives up
for (const int depth :
{
1, 5, 47, 48, 49, 50, 60, 300
})
{
CAPTURE(depth)
std::string head;
std::string tail;
for (int i = 0; i < depth; ++i)
{
head += i % 2 == 0 ? R"({"k":[1,"s",{"x":true},)" : R"([null,2.5,{"y":)";
tail = (i % 2 == 0 ? "]}" : "}]") + tail;
}
// the same text, one number changed, and one number written differently
const std::string text = head + "7" + tail;
const std::string changed = head + "8" + tail;
const std::string spelled = head + "7.0" + tail;
const json_document a = json_document::parse(text);
const json_document b = json_document::parse(text);
const json_document c = json_document::parse(changed);
const json_document d = json_document::parse(spelled);
CHECK(a.root() == b.root());
CHECK(a.root() != c.root());
CHECK(c.root() != a.root());
CHECK(a.root() == d.root());
CHECK(d.root() == a.root());
CHECK(a.root() == json::parse(text));
CHECK(a.root() != json::parse(changed));
CHECK(json::parse(spelled) == a.root());
const nlohmann::json_editable_document e = nlohmann::json_editable_document::parse(text);
CHECK(a.root() == e.root());
CHECK(e.root() == a.root());
CHECK(c.root() != e.root());
CHECK(e.root() == e.root());
const ordered_json_document oa = ordered_json_document::parse(text);
const ordered_json_document oc = ordered_json_document::parse(changed);
CHECK(oa.root() != oc.root());
CHECK(oa.root() == ordered_json::parse(text));
CHECK(oc.root() != ordered_json::parse(text));
}
// not the same text, but the same value
const auto same = [](const char* x, const char* y)
{
const json_document dx = json_document::parse(x);
const json_document dy = json_document::parse(y);
const bool equal = dx.root() == dy.root();
CHECK(equal == (json::parse(x) == json::parse(y)));
CHECK(equal == (dx.root() == json::parse(y)));
return equal;
};
CHECK(same(R"({"a":1,"a":2})", R"({"a":3,"a":2})"));
CHECK(same(R"({"a":1,"b":[1,{"c":2,"c":3}]})", R"({"b":[1,{"c":9,"c":3}],"a":1})"));
CHECK(!same(R"({"a":1,"a":2})", R"({"a":2,"a":3})"));
CHECK(same("[1.0,-0,0,1e2]", "[1.00,0,-0,100.0]"));
CHECK(same(R"(["\u00e9","\n"])", "[\"\xc3\xa9\",\"\\n\"]"));
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":false}]})"));
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"c":null}]})"));
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":null,"c":1}]})"));
// edited documents: moved sequences, links, and values of new nodes
nlohmann::json_editable_document edited = nlohmann::json_editable_document::parse(R"({"a":[1,2,3],"b":{"c":"d"},"e":0.5})");
const json_document plain = json_document::parse(R"({"a":[1,2,3,4],"b":{"c":"x","y":null},"e":0.5,"z":[]})");
edited.push_back(edited.root()["a"], 4);
edited.set(edited.root()["b"], "c", "x");
edited.set(edited.root()["b"], "y", nullptr);
edited.set(edited.root(), "z", json::array());
CHECK(edited.root() == plain.root());
CHECK(plain.root() == edited.root());
edited.erase(edited.root()["a"], 0);
CHECK(edited.root() != plain.root());
CHECK(edited.root()["a"] == json::parse("[2,3,4]"));
const json_document tail = json_document::parse("[2,3,4]");
CHECK(edited.root()["a"] == tail.root());
}
SECTION("large documents, with a difference at any place")
{
const auto make = [](std::size_t count, std::size_t changed, bool swap_members)
{
std::string text = R"({"b":1,"list":[)";
for (std::size_t i = 0; i < count; ++i)
{
text += (i != 0 ? "," : "");
const std::string id = std::to_string(i == changed ? i + 1 : i);
text += swap_members && i % 3 == 0 ? R"({"v":[1.5,"s",{"k":)" + id + R"(}],"id":)" + id + "}" : R"({"id":)" + id + R"(,"v":[1.5,"s",{"k":)" + id + "}]}";
}
return text + R"(],"a":{"y":2,"x":3}})";
};
const std::size_t count = 3000;
for (const std::size_t changed :
{
count, std::size_t{0}, std::size_t{1}, count / 2, count - 1
})
{
for (const bool swap :
{
false, true
})
{
CAPTURE(changed)
CAPTURE(swap)
const std::string text_a = make(count, count, false);
const std::string text_b = make(count, changed, swap);
const json_document a = json_document::parse(text_a);
const json_document b = json_document::parse(text_b);
const bool equal = changed == count;
CHECK((a.root() == b.root()) == equal);
CHECK((b.root() == a.root()) == equal);
CHECK((a.root() == json::parse(text_b)) == equal);
CHECK((json::parse(text_b) == a.root()) == equal);
CHECK((a.root()["list"] == b.root()["list"]) == equal);
const ordered_json_document oa = ordered_json_document::parse(text_a);
const ordered_json_document ob = ordered_json_document::parse(text_b);
CHECK((oa.root() == ob.root()) == (equal && !swap));
CHECK((oa.root() == ordered_json::parse(text_b)) == (equal && !swap));
}
}
}
SECTION("deep nesting")
{
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
+60
View File
@@ -682,6 +682,66 @@ TEST_CASE("json_view images: ownership")
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("json_view images: errors")
{
#if !defined(JSON_NOEXCEPTION)
SECTION("float tokens: a changed byte is accepted if the text is still a number with the digits the node records")
{
for (const char* const token :
{
"-12.375", "0.5", "123456.0", "-0.0", "9999999999999999999.5", "0.000000000000000000000001", "1.25e10", "5E-3"
})
{
CAPTURE(token)
const std::string text = std::string("[") + token + "]";
const json_document d = json_document::parse(text);
const std::vector<std::uint8_t> image = d.save();
const std::size_t at = text_at(image) + 1; // the first byte of the token
const std::uint16_t recorded = node_at(image, 1).extra;
CHECK(loaded_dump(image) == json::parse(text).dump());
for (std::size_t pos = 0; token[pos] != '\0'; ++pos)
{
for (unsigned int byte = 0; byte < 256; ++byte)
{
std::vector<std::uint8_t> changed = image;
changed[at + pos] = static_cast<std::uint8_t>(byte);
std::string t = token;
t[pos] = static_cast<char>(byte);
// the digits of the integer part and of the fraction of the changed token
std::size_t integer = 0;
std::size_t fraction = 0;
std::size_t k = (t[0] == '-') ? 1 : 0;
for (; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
{
++integer;
}
if (k < t.size() && t[k] == '.')
{
for (++k; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
{
++fraction;
}
}
// (as a JSON text, the token may have white space around it)
const bool float_token = json::accept(t) && t.find_first_of(std::string(" \t\r\n\0", 5)) == std::string::npos && (t.find('.') != std::string::npos || t.find_first_of("eE") != std::string::npos)
&& ((integer | (fraction << 8U)) == recorded);
CAPTURE(pos)
CAPTURE(byte)
std::string result;
try
{
result = loaded_dump(changed);
}
catch (const json::parse_error& e)
{
CHECK(std::string(e.what()) == check_failed);
result = "rejected";
}
CHECK((result != "rejected") == float_token);
}
}
}
}
#endif
SECTION("a literal as the root: dump() after loading")
{
for (const char* text :