Compare commits

...
Author SHA1 Message Date
Niels Lohmann 2241af3297 Declare the partner iterator in the loop initializer in diff_object_keys()
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:38:26 +02:00
Niels Lohmann f941cf7a7e Use std::array for the index buffer in append_index()
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:38:25 +02:00
Niels Lohmann 994099ca28 Diff objects with the same keys in lockstep and keep one path buffer
diff() looked up every key of the source object in the target object,
and built a new string for the path of every value it descended into.
Objects of the same size usually have the same keys in the same order,
so they are now walked together as long as that holds, without a
lookup; objects that differ fall back to the lookup from the first key
that does not match. The path is one buffer that is extended before
descending and shortened again afterwards. Objects whose members are
equal but in a different order (possible with ordered_json) still get
no operations.

Together with the previous commit, diff() of canada.json,
citm_catalog.json and twitter.json is 1.5x to 2.0x as fast; ordered_json
1.65x to 1.85x. The output is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:38:24 +02:00
Niels Lohmann 80407e8316 Build diff operations member by member
diff() created every operation from an initializer list, so each
{"op", "replace"} pair became a temporary array with two heap strings
before it was turned into an object member; an operation took about 25
allocations. The operations are now built as objects whose members are
assigned one by one. Paths of members and elements are also built in
place, without a temporary string for each escaped key or array index.

diff() of canada.json, citm_catalog.json and twitter.json, with every
eighth scalar changed, is 1.3x to 1.6x as fast; the output is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:38:23 +02:00
Niels Lohmann 72c45349c9 Apply plain JSON Patch operations without building a json_pointer
patch() built a json_pointer for every operation, which splits the path
into a vector of strings, and for "add" and "remove" a second one for
the parent. That took 20% to 45% of the time not spent copying the
document.

"add", "remove", "replace" and "test" with a path that has no escapes and
denotes existing values (the parent, for "add"), as diff() creates them,
are now applied by resolving the path in place, reusing one string for
the reference tokens. Every other operation, and every operation that
would fail, takes the existing code, so the results, the exceptions and
the state left after a failing operation are unchanged.

patch() of the diffs of canada.json, citm_catalog.json and twitter.json
is 1.02x to 1.10x as fast; the rest is the copy of the document.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:38:22 +02:00
Niels Lohmann 7ed54e3d59 Unflatten without splitting keys or walking each path from the root
unflatten() split every key into a vector of reference tokens, stored the
pointer prefixes in a tree to decide which values become arrays, and then
looked up every token of every key from the root again.

Keys are now checked by a scan that raises the same parse_error.107/108 as
split(), skipping the part a key shares with the previous one, which was
checked already. The prefixes that denote arrays are kept in a small hash
set of key slices; the array/object decision is unchanged. The walk keeps
the values along the previous key's path, so only the part of a key that
differs from the previous one is looked up. Keys of a flattened object are
usually sorted, so that part is short.

unflatten() of canada.json, citm_catalog.json and twitter.json is 1.9x to
2.3x as fast; the output and the exceptions are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:38:21 +02:00
Niels Lohmann 5f2d976129 Flatten into a std::map by inserting next to the previous entry
flatten() added every path with operator[], so each insertion into the
std::map of the result searched the tree from the root and compared long
shared path prefixes, which took half the time on canada.json. Paths come
out in nearly sorted order, so they are now inserted with emplace_hint
next to the entry added last, for object types that have it. ordered_map
keeps its append without lookup, and other object types keep
operator[].

Path segments are also appended in place, without a temporary string
for each escaped key or array index.

flatten() of canada.json, citm_catalog.json and twitter.json is 1.4x to
1.9x as fast; ordered_json is unchanged (1.05x). The output is
unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:37:46 +02:00
Niels Lohmann cd6b428ef0 Fix CI failures on develop from the API performance stack (#5860)
Fixes every CI failure develop has had since the API performance stack (#5831-#5842) was merged: compiler warnings and errors (Clang, GCC 4.9/11, MSVC 2015/2017/2022), clang-tidy, cpplint, Cppcheck, IWYU, the examples check, the no-exceptions, diagnostic-positions, implicit-conversions and strict-NUL test configurations, and the MinGW link (now with LLD).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 19:16:21 +02:00
16 changed files with 1483 additions and 590 deletions

No files matched your search

+10 -8
View File
@@ -166,19 +166,21 @@ jobs:
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash # link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
# before doctest prints its first line - 39 of 102 tests on clang 18. # before doctest prints its first line - 39 of 102 tests on clang 18.
# Keep the objects small by splitting the test files instead. # Keep the objects small by splitting the test files instead.
# Objects with more than 65535 sections fail to link with "relocation # The tests link with LLD: GNU ld fails on objects with more than 32767
# truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'": the MinGW # sections with "relocation truncated to fit: IMAGE_REL_AMD64_REL32
# linker stores the section an associative COMDAT section belongs to in # against `.rdata'", as it stores the section an associative COMDAT
# 16 bits (x_associated in binutils' include/coff/internal.h), so it # section belongs to in 16 bits (x_associated in binutils'
# discards the jump tables of inline functions together with the wrong # include/coff/internal.h) and so discards the jump tables of inline
# function. Each basic_json specialization a test file instantiates adds # functions together with the wrong function. Each basic_json
# many sections, so test a second one in a file of its own. # specialization and input type a test file instantiates adds many
# sections, so unit-msgpack.cpp and unit-regression2.cpp kept crossing
# that limit as the library grew.
- name: Run CMake - name: Run CMake
run: cmake -S . -B build ^ run: cmake -S . -B build ^
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^ -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^ -DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^ -DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^ -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld -lwinpthread" ^
-G"MinGW Makefiles" ^ -G"MinGW Makefiles" ^
-DCMAKE_BUILD_TYPE=Debug ^ -DCMAKE_BUILD_TYPE=Debug ^
-DJSON_BuildTests=On -DJSON_BuildTests=On
@@ -13,7 +13,7 @@
#include <optional> // optional #include <optional> // optional
#endif #endif
#include <iterator> // begin, end #include <iterator> // begin, end, make_move_iterator, move_iterator
#include <memory> // allocator_traits #include <memory> // allocator_traits
#include <string> // basic_string, char_traits #include <string> // basic_string, char_traits
#include <tuple> // tuple, get #include <tuple> // tuple, get
@@ -48,6 +48,32 @@ namespace detail
* leave j with the type of the new value, but the pointer to the destroyed old one. * leave j with the type of the new value, but the pointer to the destroyed old one.
*/ */
// a move iterator over the elements of an rvalue container, unless they are
// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a
// std::move_iterator as value_type&&, which cannot bind to a const element
template<typename Iterator>
std::move_iterator<Iterator> make_move_iterator_if_mutable_impl(const Iterator& it, std::true_type /*mutable*/)
{
return std::make_move_iterator(it);
}
template<typename Iterator>
Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/)
{
return it;
}
template<typename Iterator>
using is_mutable_iterator = std::integral_constant < bool,
!std::is_const<typename std::remove_reference<decltype(*std::declval<Iterator&>())>::type>::value >;
template<typename Iterator>
auto make_move_iterator_if_mutable(const Iterator& it)
-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {}))
{
return make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {});
}
template<value_t> struct external_constructor; template<value_t> struct external_constructor;
template<> template<>
@@ -174,7 +200,7 @@ struct external_constructor<value_t::array>
using std::begin; using std::begin;
using std::end; 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)))); const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr))));
j.replace_value(value_t::array, value); j.replace_value(value_t::array, value);
} }
@@ -235,7 +261,7 @@ struct external_constructor<value_t::object>
using std::begin; using std::begin;
using std::end; 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)))); const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj))));
j.replace_value(value_t::object, value); j.replace_value(value_t::object, value);
} }
@@ -413,25 +439,27 @@ using is_movable_container_rvalue = negation<disjunction<
std::is_const<T>, std::is_const<T>,
has_dedicated_to_json<T>, has_dedicated_to_json<T>,
is_basic_json<T>, is_basic_json<T>,
is_range_view_type<T>,
is_enum_keyed_map_type<T>, is_enum_keyed_map_type<T>,
std::is_same<T, std::vector<bool>>>>; std::is_same<T, std::vector<bool>>>>;
// rvalue array-like containers // rvalue array-like containers; object types are ruled out before views are
// checked, as std::ranges::view<T> for the object_t of an ordered_json would
// need that ordered_json to be complete (GCC 11, see #3312)
template<typename BasicJsonType, typename T> template<typename BasicJsonType, typename T>
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>, using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
negation<is_compatible_object_type<BasicJsonType, T>>,
negation<is_range_view_type<T>>,
is_compatible_array_type<BasicJsonType, T>, is_compatible_array_type<BasicJsonType, T>,
negation<disjunction< negation<disjunction<
std::is_same<T, typename BasicJsonType::array_t>, std::is_same<T, typename BasicJsonType::array_t>,
std::is_same<T, typename BasicJsonType::binary_t>, std::is_same<T, typename BasicJsonType::binary_t>,
is_compatible_object_type<BasicJsonType, T>,
is_compatible_string_type<BasicJsonType, T>, is_compatible_string_type<BasicJsonType, T>,
is_compatible_binary_type<BasicJsonType, T>>>>; is_compatible_binary_type<BasicJsonType, T>>>>;
// rvalue containers: their elements are moved instead of copied // rvalue containers: their elements are moved instead of copied
template<typename BasicJsonType, typename CompatibleArrayType, template<typename BasicJsonType, typename CompatibleArrayType,
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0> enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
external_constructor<value_t::array>::construct_moving(j, arr); external_constructor<value_t::array>::construct_moving(j, arr);
} }
@@ -509,7 +537,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
} }
#endif #endif
// rvalue object-like containers // rvalue object-like containers (views have no key_type and mapped_type, so
// they are no compatible object type)
template<typename BasicJsonType, typename T> template<typename BasicJsonType, typename T>
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>, using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
negation<std::is_same<T, typename BasicJsonType::object_t>>, negation<std::is_same<T, typename BasicJsonType::object_t>>,
@@ -517,7 +546,7 @@ using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
template<typename BasicJsonType, typename CompatibleObjectType, template<typename BasicJsonType, typename CompatibleObjectType,
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0> enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
external_constructor<value_t::object>::construct_moving(j, obj); external_constructor<value_t::object>::construct_moving(j, obj);
} }
+1 -1
View File
@@ -185,7 +185,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift)); const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift); std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses) // (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
constexpr unsigned fractional_low_shift = static_cast<unsigned>(extra_shift); constexpr auto fractional_low_shift = static_cast<unsigned>(extra_shift);
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift; constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift); const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
std::uint64_t digit = 0; std::uint64_t digit = 0;
+1 -2
View File
@@ -494,8 +494,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c
static_cast<void>(string_type); static_cast<void>(string_type);
static_cast<void>(val); static_cast<void>(val);
// context-sensitive error message // context-sensitive error message
const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\''); JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val));
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
} }
template<typename StringType, typename BasicJsonType> template<typename StringType, typename BasicJsonType>
@@ -680,12 +680,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->is_object())) if (JSON_HEDLEY_UNLIKELY(!m_object->is_object()))
{ {
return m_it.object_iterator->first; throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
} }
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); return m_it.object_iterator->first;
} }
/*! /*!
+377 -184
View File
@@ -9,9 +9,13 @@
#pragma once #pragma once
#include <algorithm> // all_of #include <algorithm> // all_of
#include <array> // array
#include <cctype> // isdigit #include <cctype> // isdigit
#include <cerrno> // errno, ERANGE #include <cerrno> // errno, ERANGE
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <cstdlib> // strtoull #include <cstdlib> // strtoull
#include <cstring> // memcmp, memcpy
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
#include <iosfwd> // ostream #include <iosfwd> // ostream
#endif // JSON_NO_IO #endif // JSON_NO_IO
@@ -334,7 +338,7 @@ class json_pointer
{ {
break; // not a plain number: let strtoull decide break; // not a plain number: let strtoull decide
} }
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0')); acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0'));
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1)) if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
{ {
idx = acc; idx = acc;
@@ -491,50 +495,111 @@ class json_pointer
private: private:
/*! /*!
@brief the pointer prefixes of a flattened object, and which of them denote arrays @brief the pointer prefixes of a flattened object that denote arrays
@ref unflatten collects the pointer prefixes that have a reference token 0 @ref unflatten creates arrays exactly below the prefixes that have a
among their children; @ref get_and_create creates arrays exactly below reference token 0 among their children, and objects everywhere else.
those prefixes and objects everywhere else. Deciding this up front keeps Deciding this up front keeps the result independent of the order in which
the result independent of the order in which the flattened object is the flattened object is iterated, which is unspecified for some object
iterated, which is unspecified for some object types. types.
The prefixes form a tree and are numbered (number 0 is the empty prefix), A prefix is identified by the text of the key it is the beginning of:
so each of them is stored only once (as a node) rather than as a copy of escaping is unambiguous, so equal prefixes of valid keys are equal texts.
all of its reference tokens. The tree does not own the reference tokens: The set refers to the keys, which must outlive it.
it refers to the tokens of the parsed keys, which must outlive it.
*/ */
struct prefix_tree class array_prefix_set
{ {
// open-addressing hash table from (parent, last reference token) to public:
// the number of the node; empty slots have token == nullptr void insert(const char* data, std::size_t size)
{
// flattened objects are usually sorted, so most keys that have a
// 0 yield the prefix of the previous one
if (last_data != nullptr && last_size == size && std::memcmp(last_data, data, size) == 0)
{
return;
}
last_data = data;
last_size = size;
if (slots.empty())
{
slots.resize(16);
}
const std::size_t hash = hash_of(data, size);
const std::size_t mask = slots.size() - 1;
std::size_t i = hash & mask;
while (slots[i].data != nullptr)
{
if (slots[i].hash == hash && slots[i].size == size && std::memcmp(slots[i].data, data, size) == 0)
{
return;
}
i = (i + 1) & mask;
}
slots[i] = slot(hash, data, size);
// keep the load factor below 1/2
if (++used * 2 > slots.size())
{
grow();
}
}
bool empty() const noexcept
{
return used == 0;
}
bool contains(const char* data, std::size_t size) const noexcept
{
if (used == 0)
{
return false;
}
const std::size_t hash = hash_of(data, size);
const std::size_t mask = slots.size() - 1;
for (std::size_t i = hash & mask; slots[i].data != nullptr; i = (i + 1) & mask)
{
if (slots[i].hash == hash && slots[i].size == size && std::memcmp(slots[i].data, data, size) == 0)
{
return true;
}
}
return false;
}
private:
struct slot struct slot
{ {
std::size_t hash; slot() noexcept = default;
std::size_t parent;
const string_t* token;
std::size_t id;
slot() noexcept slot(std::size_t h, const char* d, std::size_t s) noexcept
: hash(0), parent(0), token(nullptr), id(0) : hash(h), data(d), size(s)
{} {}
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept std::size_t hash = 0;
: hash(h), parent(p), token(t), id(i) const char* data = nullptr; // nullptr: the slot is empty
{} std::size_t size = 0;
}; };
std::vector<slot> slots; static std::size_t hash_of(const char* data, std::size_t size) noexcept
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); std::uint64_t h = 0x9E3779B97F4A7C15ULL ^ size;
for (std::size_t i = 0; i < token.size(); ++i) std::size_t i = 0;
for (; i + 8 <= size; i += 8)
{ {
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL); std::uint64_t w = 0;
std::memcpy(&w, data + i, 8);
h = (h ^ w) * 0x100000001B3ULL;
h ^= h >> 32u;
} }
return h ^ (h >> 29u); std::uint64_t w = 0;
if (i < size)
{
std::memcpy(&w, data + i, size - i);
}
h = (h ^ w) * 0x9E3779B97F4A7C15ULL;
return static_cast<std::size_t>(h ^ (h >> 29u));
} }
void grow() void grow()
@@ -544,10 +609,10 @@ class json_pointer
const std::size_t mask = slots.size() - 1; const std::size_t mask = slots.size() - 1;
for (const auto& s : old) for (const auto& s : old)
{ {
if (s.token != nullptr) if (s.data != nullptr)
{ {
std::size_t i = s.hash & mask; std::size_t i = s.hash & mask;
while (slots[i].token != nullptr) while (slots[i].data != nullptr)
{ {
i = (i + 1) & mask; i = (i + 1) & mask;
} }
@@ -556,117 +621,160 @@ class json_pointer
} }
} }
// parent[id] is the number of the prefix without its last token std::vector<slot> slots{};
std::vector<std::size_t> parent; std::size_t used = 0;
// is_array[id] is true iff some flattened key has the reference token const char* last_data = nullptr;
// 0 directly below the prefix with number id std::size_t last_size = 0;
std::vector<bool> is_array;
// start with the empty prefix only
prefix_tree()
: slots(16)
, parent(1, 0)
, is_array(1, false)
{}
// return the number of the prefix with number id extended by
// reference_token, adding it if it is new
std::size_t add_child(std::size_t id, const 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")
{
is_array[id] = true;
}
parent.push_back(id);
is_array.push_back(false);
// keep the load factor below 1/2
if (++used * 2 > slots.size())
{
grow();
}
return child;
}
}; };
/// @brief the length of the longest common prefix of the first @a limit bytes of @a a and @a b
static std::size_t common_prefix_length(const char* a, const char* b, const std::size_t limit) noexcept
{
std::size_t i = 0;
for (; i + 8 <= limit; i += 8)
{
std::uint64_t x = 0;
std::uint64_t y = 0;
std::memcpy(&x, a + i, 8);
std::memcpy(&y, b + i, 8);
if (x != y)
{
break;
}
}
while (i < limit && a[i] == b[i])
{
++i;
}
return i;
}
/*! /*!
@brief create and return a reference to the pointed to value @brief check that @a key is a valid JSON pointer, and add the prefixes of
it that denote arrays to @a arrays
@param[in] j the value to create the pointed to value in @throw parse_error.107 if the key is not empty or begins with '/'
@param[in] tokens the reference tokens of the pointer @throw parse_error.108 if character '~' is not followed by '0' or '1'
@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. The same checks as @ref split, which throws the same exceptions, but
without building the reference tokens.
*/
static void scan_key(const string_t& key, const string_t* previous, array_prefix_set& arrays)
{
const std::size_t size = key.size();
if (size == 0)
{
return;
}
@throw parse_error.106 if an array index begins with '0' const char* const data = key.data();
@throw parse_error.109 if array index is not a number if (JSON_HEDLEY_UNLIKELY(data[0] != '/'))
@throw type_error.313 if value cannot be unflattened {
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", key, "'"), nullptr));
}
// The beginning of the key that it has in common with the previous
// one, which was checked already, is skipped: the scan starts with
// the token after the last slash in it.
std::size_t begin = 1;
if (previous != nullptr)
{
std::size_t same = common_prefix_length(data, previous->data(), (std::min)(size, previous->size()));
while (same > 1 && data[same - 1] != '/')
{
--same;
}
if (same > 1)
{
begin = same;
}
}
for (std::size_t i = begin; i <= size; ++i)
{
if (i == size || data[i] == '/')
{
// a token 0 makes the pointer before it an array
if (i - begin == 1 && data[begin] == '0')
{
arrays.insert(data, begin - 1);
}
begin = i + 1;
}
else if (data[i] == '~')
{
// ~ must be followed by 0 or 1
if (JSON_HEDLEY_UNLIKELY(i + 1 == size || (data[i + 1] != '0' && data[i + 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));
}
}
}
}
/*!
@brief resolve a plain JSON pointer without building its reference tokens
@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
@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: 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> template<typename BasicJsonType>
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens, static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
{ {
auto* result = &j; for (std::size_t pos = 0; pos < length;)
// in case no reference tokens exist, return a reference to the JSON value
// j which will be overwritten by a primitive value
for (std::size_t i = 0; i < tokens.size(); ++i)
{ {
const auto& reference_token = tokens[i]; if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
switch (result->type())
{ {
case detail::value_t::null: return nullptr;
{ }
if (tree.is_array[prefixes[i]])
{
// some reference token below this position is 0, so the
// value is an array
result = &result->operator[](array_index<BasicJsonType>(reference_token));
}
else
{
// start a new object otherwise
result = &result->operator[](reference_token);
}
break;
}
// 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)
{
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: case detail::value_t::object:
{ {
// create an entry in the object const auto it = ptr->m_data.m_value.object->find(token);
result = &object_member(*result, reference_token); if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{
return nullptr;
}
ptr = &it->second;
break; break;
} }
case detail::value_t::array: case detail::value_t::array:
{ {
// create an entry in the array typename BasicJsonType::size_type idx{};
result = &result->operator[](array_index<BasicJsonType>(reference_token)); if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
ptr = &(*ptr->m_data.m_value.array)[idx];
break; break;
} }
/* case detail::value_t::null:
The following code is only reached if there exists a reference
token _and_ the current value is primitive. In this case, we have
an error situation, because primitive values may only occur as
a single value; that is, with an empty list of reference tokens.
*/
case detail::value_t::string: case detail::value_t::string:
case detail::value_t::boolean: case detail::value_t::boolean:
case detail::value_t::number_integer: case detail::value_t::number_integer:
@@ -675,11 +783,12 @@ class json_pointer
case detail::value_t::binary: case detail::value_t::binary:
case detail::value_t::discarded: case detail::value_t::discarded:
default: default:
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); return nullptr;
} }
pos = end;
} }
return *result; return ptr;
} }
/*! /*!
@@ -1134,20 +1243,45 @@ class json_pointer
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise // 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 // distinct, so objects that can append a key known to be absent
// (ordered_map) skip the lookup, which is linear and would make flatten // (ordered_map) skip the lookup, which is linear and would make flatten
// quadratic; all other object types use operator[]. // quadratic. Objects with a positional insert (std::map) insert next to
// the entry added last: paths come out in nearly sorted order, so the
// position is usually right and no search from the root is needed.
// All other object types use operator[].
template<typename ObjectType, typename ValueType> template<typename ObjectType, typename ValueType>
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<1> /*unused*/) static auto add_flattened(ObjectType& obj, typename ObjectType::iterator& /*hint*/, const string_t& key, ValueType&& v, detail::priority_tag<2> /*unused*/)
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void()) -> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
{ {
obj.emplace_new_key(key, std::forward<ValueType>(v)); obj.emplace_new_key(key, std::forward<ValueType>(v));
} }
template<typename ObjectType, typename ValueType> template<typename ObjectType, typename ValueType>
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<0> /*unused*/) static auto add_flattened(ObjectType& obj, typename ObjectType::iterator& hint, const string_t& key, ValueType&& v, detail::priority_tag<1> /*unused*/)
-> decltype(hint = obj.emplace_hint(hint, key, std::forward<ValueType>(v)), void())
{
hint = obj.emplace_hint(hint, key, std::forward<ValueType>(v));
++hint;
}
template<typename ObjectType, typename ValueType>
static void add_flattened(ObjectType& obj, typename ObjectType::iterator& /*hint*/, const string_t& key, ValueType&& v, detail::priority_tag<0> /*unused*/)
{ {
obj[key] = std::forward<ValueType>(v); obj[key] = std::forward<ValueType>(v);
} }
/// append the decimal representation of @a i to @a out
static void append_index(string_t& out, std::size_t i)
{
std::array<char, 24> buf{};
std::size_t pos = buf.size();
do
{
buf[--pos] = static_cast<char>('0' + i % 10);
i /= 10;
}
while (i != 0);
out.append(buf.data() + pos, buf.size() - pos);
}
/*! /*!
@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
@@ -1193,7 +1327,8 @@ 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; auto& object = *result.m_data.m_value.object;
const auto enter = [&stack, &path, &object](const BasicJsonType & v) typename BasicJsonType::object_t::iterator hint = object.end();
const auto enter = [&stack, &path, &object, &hint](const BasicJsonType & v)
{ {
switch (v.type()) switch (v.type())
{ {
@@ -1202,7 +1337,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, detail::priority_tag<1> {}); add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
} }
else else
{ {
@@ -1216,7 +1351,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, detail::priority_tag<1> {}); add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
} }
else else
{ {
@@ -1236,7 +1371,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, detail::priority_tag<1> {}); add_flattened(object, hint, path, v, detail::priority_tag<2> {});
return; return;
} }
} }
@@ -1264,7 +1399,8 @@ class json_pointer
// iterate array and use index as a reference string // iterate array and use index as a reference string
++stack.back().index; ++stack.back().index;
detail::concat_into(path, '/', detail::to_string<string_t>(i)); path.push_back('/');
append_index(path, i);
enter(array[i]); enter(array[i]);
} }
else else
@@ -1278,7 +1414,8 @@ class json_pointer
// iterate object and use keys as reference string // iterate object and use keys as reference string
++stack.back().member; ++stack.back().member;
detail::concat_into(path, '/', detail::escape(it->first)); path.push_back('/');
detail::escape_into(path, it->first);
enter(it->second); enter(it->second);
} }
} }
@@ -1309,53 +1446,30 @@ class json_pointer
BasicJsonType result; BasicJsonType result;
// parse every key once; the reference tokens are kept for the second // check every key, and collect the prefixes that have a reference
// pass (the vectors are never resized, so the tree may refer to them) // token 0 among their children; the values below them are arrays,
std::vector<std::vector<string_t>> keys; // all others are objects (see array_prefix_set)
keys.reserve(value.m_data.m_value.object->size()); array_prefix_set arrays;
const string_t* previous_key = nullptr;
for (const auto& element : *value.m_data.m_value.object) for (const auto& element : *value.m_data.m_value.object)
{ {
json_pointer ptr(element.first); scan_key(element.first, previous_key, arrays);
keys.push_back(std::move(ptr.reference_tokens)); previous_key = &element.first;
} }
// collect the pointer prefixes that have a reference token 0 among // values[i] is the value after the first i reference tokens of the
// their children; the values below them are arrays, all others are // previous key, ends[i] the length of that prefix of the key. Keys are
// objects (see prefix_tree) // often sorted, so most of the path to the value of a key is the
prefix_tree tree; // same as for the previous key and need not be looked up again. Only
// the number of the prefix with i + 1 tokens of the previous key; keys // the values below the common prefix are dropped, and only they can
// are often sorted, so most of the prefix is the same as for the // move when a value is added: the ones above are not inside the
// previous key and need not be looked up again // container that grows.
std::vector<std::size_t> path; std::vector<BasicJsonType*> values(1, &result);
const std::vector<string_t>* previous = nullptr; std::vector<std::size_t> ends(1, 0);
// the number of the complete prefix (i.e., the key) of each key const string_t* previous = nullptr;
std::vector<std::size_t> leaves; string_t token;
leaves.reserve(keys.size());
for (const auto& tokens : keys)
{
std::size_t common = 0;
if (previous != nullptr)
{
const std::size_t limit = (std::min)(tokens.size(), previous->size());
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()))
@@ -1363,21 +1477,100 @@ 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]; const string_t& key = element.first;
// number the prefixes of this key from its last one to the first const char* const data = key.data();
prefixes.resize(tokens.size());
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i) // the number of reference tokens the key has in common with the
// previous one
std::size_t depth = 0;
if (previous != nullptr)
{ {
id = tree.parent[id]; const std::size_t same = common_prefix_length(data, previous->data(), (std::min)(key.size(), previous->size()));
prefixes[i - 1] = id; depth = ends.size() - 1;
while (depth > 0 && (ends[depth] > same || (ends[depth] == same && same < key.size() && data[same] != '/')))
{
--depth;
}
} }
++key_index; values.resize(depth + 1);
ends.resize(depth + 1);
previous = &key;
// 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. // the value is the result itself. An assignment will then create a
// An assignment will then create a primitive value. // primitive value.
get_and_create(result, tokens, prefixes, tree) = element.second; BasicJsonType* current = values.back();
for (std::size_t pos = ends.back(); pos < key.size();)
{
// the reference token between the slash at pos and the next
std::size_t end = key.find_first_of('/', pos + 1);
if (end == string_t::npos)
{
end = key.size();
}
token.clear();
token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos)
{
detail::unescape(token);
}
switch (current->type())
{
case detail::value_t::null:
{
if (arrays.contains(data, pos))
{
// some reference token below this position is 0, so the
// value is an array
current = &current->operator[](array_index<BasicJsonType>(token));
}
else
{
// start a new object otherwise
current = &current->operator[](token);
}
break;
}
case detail::value_t::object:
{
// create an entry in the object
current = &object_member(*current, token);
break;
}
case detail::value_t::array:
{
// create an entry in the array
current = &current->operator[](array_index<BasicJsonType>(token));
break;
}
/*
The following code is only reached if there exists a reference
token _and_ the current value is primitive. In this case, we have
an error situation, because primitive values may only occur as
a single value; that is, with an empty list of reference tokens.
*/
case detail::value_t::string:
case detail::value_t::boolean:
case detail::value_t::number_integer:
case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &result));
}
values.push_back(current);
ends.push_back(end);
pos = end;
}
*current = element.second;
} }
return result; return result;
+292 -79
View File
@@ -29,6 +29,7 @@
#endif // JSON_NO_IO #endif // JSON_NO_IO
#include <iterator> // make_move_iterator, random_access_iterator_tag #include <iterator> // make_move_iterator, random_access_iterator_tag
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <map> // map
#include <memory> // unique_ptr #include <memory> // unique_ptr
#include <set> // swap, operator!= #include <set> // swap, operator!=
#include <stdexcept> // length_error #include <stdexcept> // length_error
@@ -754,26 +755,20 @@ private:
// one level deep); stops at the first structured child it meets // one level deep); stops at the first structured child it meets
static bool all_children_leaves(const basic_json& v) noexcept static bool all_children_leaves(const basic_json& v) noexcept
{ {
// array_t only needs forward iterators (see custom_array_type)
if (v.m_data.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
const auto& a = *v.m_data.m_value.array; const auto& a = *v.m_data.m_value.array;
for (auto it = a.rbegin(); it != a.rend(); ++it) return std::all_of(a.begin(), a.end(), [](const basic_json & el)
{ {
if (!has_no_children(*it)) return has_no_children(el);
{ });
return false;
}
}
return true;
} }
for (const auto& el : *v.m_data.m_value.object) const auto& o = *v.m_data.m_value.object;
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
{ {
if (!has_no_children(el.second)) return has_no_children(el.second);
{ });
return false;
}
}
return true;
} }
// The walk in destroy_container() may take the children of a // The walk in destroy_container() may take the children of a
@@ -1230,7 +1225,7 @@ public:
} }
template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 > template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 >
static constexpr std::size_t object_capacity() noexcept std::size_t object_capacity() const noexcept
{ {
return 0; return 0;
} }
@@ -1242,7 +1237,7 @@ public:
} }
template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 > template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 >
static constexpr std::size_t object_capacity() noexcept std::size_t object_capacity() const noexcept
{ {
return detail::unknown_size(); return detail::unknown_size();
} }
@@ -3043,18 +3038,21 @@ public:
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_iterator_range_of_null(const basic_json* obj) static void throw_iterator_range_of_null(const basic_json* obj)
{ {
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj));
} }
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_incompatible_reference_type(const basic_json& obj) static void throw_incompatible_reference_type(const basic_json& obj)
{ {
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
} }
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_array_index_exceeds_size_type(size_type idx) static void throw_array_index_exceeds_size_type(size_type idx)
{ {
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
} }
@@ -3062,6 +3060,8 @@ public:
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_array_index_out_of_range(size_type idx, const basic_json& j) static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
{ {
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j);
JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
} }
@@ -3070,6 +3070,8 @@ public:
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_key_not_found(const KeyType& key, const basic_json& j) static void throw_key_not_found(const KeyType& key, const basic_json& j)
{ {
static_cast<void>(key); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j);
JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j));
} }
@@ -4893,19 +4895,19 @@ public:
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
{ {
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
// check if iterator pos fits to this JSON value detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, val);
} }
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, val);
} }
/// @brief inserts element into array /// @brief inserts element into array
@@ -4913,21 +4915,21 @@ public:
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
{ {
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
// check if iterator pos fits to this JSON value detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// moving into a local first keeps this safe even if val aliases
// an element of this array
basic_json tmp(std::move(val));
return insert_iterator(pos, std::move(tmp));
} }
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// moving into a local first keeps this safe even if val aliases
// an element of this array
basic_json tmp(std::move(val));
return insert_iterator(pos, std::move(tmp));
} }
/// @brief inserts copies of element into array /// @brief inserts copies of element into array
@@ -4935,19 +4937,19 @@ public:
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
{ {
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
// check if iterator pos fits to this JSON value detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, cnt, val);
} }
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, cnt, val);
} }
/// @brief inserts range of elements into array /// @brief inserts range of elements into array
@@ -5453,7 +5455,7 @@ public:
// inline fast path for two values of the same non-owning scalar type; anything // inline fast path for two values of the same non-owning scalar type; anything
// else falls through to the out-of-line general comparison // else falls through to the out-of-line general comparison
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \ #define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
if (lhs.m_data.m_type == rhs.m_data.m_type) \ if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \
{ \ { \
switch (lhs.m_data.m_type) \ switch (lhs.m_data.m_type) \
{ \ { \
@@ -7064,6 +7066,125 @@ public:
detail::throw_patch_not_an_array(json_patch); detail::throw_patch_not_an_array(json_patch);
} }
// the usual patch operations "add", "remove", "replace" and "test" on
// a pointer without escapes that denotes existing array elements and
// object members (as "add" does for the parent of the new value), as
// a diff creates them: apply the operation and return true; return
// false, with nothing changed, for all others
string_t token;
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() || path.find_first_of('~') != string_t::npos)
{
return false;
}
// a missing "value" is reported below
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;
}
switch (operation)
{
case patch_operations::replace:
{
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr)
{
return false;
}
*target = value;
return true;
}
case patch_operations::test:
{
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;
}
case patch_operations::add:
case patch_operations::remove:
{
// the parent of the pointer and its last reference token
std::size_t slash = path.size();
while (slash > 0 && path[slash - 1] != '/')
{
--slash;
}
if (slash == 0)
{
return false;
}
--slash;
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 (operation == patch_operations::add)
{
if (parent->is_object())
{
(*parent)[token] = value;
return true;
}
if (parent->is_array())
{
typename basic_json::size_type idx{};
if (token == "-")
{
parent->push_back(value);
return true;
}
if (json_pointer::template parse_array_index<basic_json_t>(token, idx) == json_pointer::array_index_status::ok && idx <= parent->size())
{
parent->insert(parent->begin() + static_cast<difference_type>(idx), value);
return true;
}
}
return false;
}
if (parent->is_object())
{
const auto it = parent->find(token);
if (it == parent->end())
{
return false;
}
parent->erase(it);
return true;
}
if (parent->is_array())
{
typename basic_json::size_type idx{};
if (json_pointer::template parse_array_index<basic_json_t>(token, idx) == json_pointer::array_index_status::ok && idx < parent->size())
{
parent->erase(idx);
return true;
}
}
return false;
}
case patch_operations::move:
case patch_operations::copy:
case patch_operations::invalid:
default:
return false;
}
};
// iterate and apply the operations // iterate and apply the operations
for (const auto& val : json_patch) for (const auto& val : json_patch)
{ {
@@ -7100,9 +7221,19 @@ public:
// collect mandatory members // collect mandatory members
const string_t& op = *get_value("op", "op", 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 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, val))
{
continue;
}
json_pointer ptr(path); json_pointer ptr(path);
switch (get_op(op)) switch (operation)
{ {
case patch_operations::add: case patch_operations::add:
{ {
@@ -7210,7 +7341,8 @@ public:
{ {
// the patch // the patch
basic_json result(value_t::array); basic_json result(value_t::array);
diff_recursively(result, source, target, path, 0); string_t buffer = path;
diff_recursively(result, source, target, buffer, 0);
return result; return result;
} }
@@ -7254,31 +7386,69 @@ public:
// function it appears in. In diff_recursively, which is on the call stack // function it appears in. In diff_recursively, which is on the call stack
// once per nesting level, that made every level cost kilobytes of stack. // once per nesting level, that made every level cost kilobytes of stack.
/// @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)
{
basic_json entry(value_t::object);
entry["op"] = op;
entry["path"] = path;
if (value != nullptr)
{
entry["value"] = *value;
}
result.push_back(std::move(entry));
}
/// @brief append a "replace" operation for @a path with @a value to @a result /// @brief append a "replace" operation for @a path with @a value to @a result
static void diff_replace(basic_json& result, const string_t& path, const basic_json& value) static void diff_replace(basic_json& result, const string_t& path, const basic_json& value)
{ {
result.push_back( diff_operation(result, "replace", path, &value);
{
{"op", "replace"}, {"path", path}, {"value", value}
});
} }
/// @brief append a "remove" operation for @a path to @a result /// @brief append a "remove" operation for @a path to @a result
static void diff_remove(basic_json& result, const string_t& path) static void diff_remove(basic_json& result, const string_t& path)
{ {
result.push_back(object( diff_operation(result, "remove", path, nullptr);
{
{"op", "remove"}, {"path", path}
}));
} }
/// @brief append an "add" operation for @a path with @a value to @a result /// @brief append an "add" operation for @a path with @a value to @a result
static void diff_add(basic_json& result, const string_t& path, const basic_json& value) static void diff_add(basic_json& result, const string_t& path, const basic_json& value)
{ {
result.push_back( diff_operation(result, "add", path, &value);
{ }
{"op", "add"}, {"path", path}, {"value", value}
}); /// @brief append "/" and the decimal representation of @a index to @a path
static void diff_append_index(string_t& path, std::size_t index)
{
path.push_back('/');
json_pointer::append_index(path, index);
}
/// @brief append "/" and the escaped @a key to @a path
static void diff_append_key(string_t& path, const string_t& key)
{
path.push_back('/');
detail::escape_into(path, key);
}
/// @brief the path of the member @a key of the value at @a path
static string_t diff_key_path(const string_t& path, const string_t& key)
{
string_t result;
result.reserve(path.size() + key.size() + 1);
result.append(path.data(), path.size());
diff_append_key(result, key);
return result;
}
/// @brief the path of the element @a index of the value at @a path
static string_t diff_index_path(const string_t& path, const std::size_t index)
{
string_t result;
result.reserve(path.size() + 8);
result.append(path.data(), path.size());
diff_append_index(result, index);
return result;
} }
/// @brief append the "remove" operations for the elements of array /// @brief append the "remove" operations for the elements of array
@@ -7291,13 +7461,18 @@ public:
// in that order avoids the quadratic reinsertion done before // in that order avoids the quadratic reinsertion done before
for (std::size_t j = source.size(); j > index; --j) for (std::size_t j = source.size(); j > index; --j)
{ {
diff_remove(result, detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1))); diff_remove(result, diff_index_path(path, j - 1));
} }
// add other remaining elements // add other remaining elements
for (std::size_t i = source.size(); i < target.size(); ++i) if (source.size() < target.size())
{ {
diff_add(result, detail::concat<string_t>(path, "/-"), target[i]); string_t tail_path = path;
tail_path.append("/-", 2);
for (std::size_t i = source.size(); i < target.size(); ++i)
{
diff_add(result, tail_path, target[i]);
}
} }
} }
@@ -7306,7 +7481,7 @@ public:
{ {
for (const auto& it : added) for (const auto& it : added)
{ {
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value()); diff_add(result, diff_key_path(path, it.key()), it.value());
} }
} }
@@ -7327,10 +7502,25 @@ public:
std::vector<const_iterator>& added) std::vector<const_iterator>& added)
{ {
// first pass: one lookup per source key // first pass: one lookup per source key
// Objects of the same size usually have the same keys, in the same
// order: walk them together as long as that holds, which takes no lookups.
target_values.reserve(source.size()); target_values.reserve(source.size());
std::size_t common = 0; std::size_t common = 0;
for (auto it = source.cbegin(); it != source.cend(); ++it) bool together = source.size() == target.size();
for (auto it = source.cbegin(), partner = target.cbegin(); it != source.cend(); ++it)
{ {
if (together)
{
if (it.key() == partner.key())
{
target_values.push_back(&partner.value());
++common;
++partner;
continue;
}
together = false;
}
const auto found = target.find(it.key()); const auto found = target.find(it.key());
if (found != target.cend()) if (found != target.cend())
{ {
@@ -7343,6 +7533,12 @@ public:
} }
} }
// the same keys in the same order
if (together)
{
return true;
}
// second pass: find the keys only target has. An object type that // second pass: find the keys only target has. An object type that
// keeps its members in insertion order, such as the one backing // keeps its members in insertion order, such as the one backing
// ordered_json, also needs the common keys to be in the same order // ordered_json, also needs the common keys to be in the same order
@@ -7408,6 +7604,13 @@ public:
return true; return true;
} }
// equal objects, whose members are in a different order, need no
// operations
if (source == target)
{
return false;
}
// slow path: the common keys are in a different relative // slow path: the common keys are in a different relative
// order in source and target (only possible for a // order in source and target (only possible for a
// reorderable object_t like ordered_map). Building a // reorderable object_t like ordered_map). Building a
@@ -7424,7 +7627,7 @@ public:
// it moves it to the end, fixing its position. // it moves it to the end, fixing its position.
for (auto it = source.cbegin(); it != source.cend(); ++it) for (auto it = source.cbegin(); it != source.cend(); ++it)
{ {
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key()))); diff_remove(result, diff_key_path(path, it.key()));
} }
// add every key that is either common (just removed // add every key that is either common (just removed
@@ -7433,7 +7636,7 @@ public:
// target exactly // target exactly
for (auto it = target.cbegin(); it != target.cend(); ++it) for (auto it = target.cbegin(); it != target.cend(); ++it)
{ {
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value()); diff_add(result, diff_key_path(path, it.key()), it.value());
} }
return false; return false;
} }
@@ -7442,6 +7645,9 @@ public:
@brief @ref diff, for values at nesting level @a depth, appending the @brief @ref diff, for values at nesting level @a depth, appending the
operations to @a result operations to @a result
@a path is the path of the values; it is a buffer that is extended and
shortened again below, and is the same when the function returns.
Diffing two arrays or objects calls this function again, once per nesting Diffing two arrays or objects calls this function again, once per nesting
level, so values nested deeply enough used to exhaust the call stack and level, so values nested deeply enough used to exhaust the call stack and
terminate the process. The descent is bounded here: once @ref terminate the process. The descent is bounded here: once @ref
@@ -7449,7 +7655,7 @@ public:
diff_iteratively diffs what is left without the call stack. diff_iteratively diffs what is left without the call stack.
*/ */
static void diff_recursively(basic_json& result, const basic_json& source, const basic_json& target, static void diff_recursively(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, const std::size_t depth) string_t& path, const std::size_t depth)
{ {
// if the values are the same, there is nothing to do // if the values are the same, there is nothing to do
if (source == target) if (source == target)
@@ -7461,9 +7667,9 @@ public:
} }
/// @brief @ref diff_recursively for values known to be unequal; callers /// @brief @ref diff_recursively for values known to be unequal; callers
/// compare first so they build no path for equal ones /// compare first so they extend the path only for unequal ones
static void diff_unequal(basic_json& result, const basic_json& source, const basic_json& target, static void diff_unequal(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, const std::size_t depth) string_t& path, const std::size_t depth)
{ {
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit())) if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{ {
@@ -7482,6 +7688,8 @@ public:
{ {
case value_t::array: case value_t::array:
{ {
const std::size_t path_length = path.size();
// first pass: traverse common elements // first pass: traverse common elements
std::size_t i = 0; std::size_t i = 0;
while (i < source.size() && i < target.size()) while (i < source.size() && i < target.size())
@@ -7489,7 +7697,9 @@ public:
// recursive call to compare array values at index i // recursive call to compare array values at index i
if (!(source[i] == target[i])) if (!(source[i] == target[i]))
{ {
diff_unequal(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1); diff_append_index(path, i);
diff_unequal(result, source[i], target[i], path, depth + 1);
path.resize(path_length);
} }
++i; ++i;
} }
@@ -7511,6 +7721,7 @@ public:
// lookup is needed. Deleted keys are interleaved here // lookup is needed. Deleted keys are interleaved here
// too, in source's original order, to match the // too, in source's original order, to match the
// historical (pre-reordering-aware) output order. // historical (pre-reordering-aware) output order.
const std::size_t path_length = path.size();
std::size_t i = 0; std::size_t i = 0;
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i) for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
{ {
@@ -7518,13 +7729,15 @@ public:
{ {
if (!(it.value() == *target_values[i])) if (!(it.value() == *target_values[i]))
{ {
diff_unequal(result, it.value(), *target_values[i], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1); diff_append_key(path, it.key());
diff_unequal(result, it.value(), *target_values[i], path, depth + 1);
path.resize(path_length);
} }
} }
else else
{ {
// found a key that is not in target -> remove it // found a key that is not in target -> remove it
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key()))); diff_remove(result, diff_key_path(path, it.key()));
} }
} }
@@ -7655,7 +7868,7 @@ public:
if (i < source_array.size() && i < target_array.size()) if (i < source_array.size() && i < target_array.size())
{ {
++stack.back().index; ++stack.back().index;
detail::concat_into(current_path, '/', detail::to_string<string_t>(i)); diff_append_index(current_path, i);
enter(source_array[i], target_array[i]); enter(source_array[i], target_array[i]);
if (stack.size() == depth) if (stack.size() == depth)
{ {
@@ -7678,7 +7891,7 @@ public:
if (target_value_ptr != nullptr) if (target_value_ptr != nullptr)
{ {
const basic_json& target_value = *target_value_ptr; const basic_json& target_value = *target_value_ptr;
detail::concat_into(current_path, '/', detail::escape(it.key())); diff_append_key(current_path, it.key());
enter(it.value(), target_value); enter(it.value(), target_value);
if (stack.size() == depth) if (stack.size() == depth)
{ {
@@ -7688,7 +7901,7 @@ public:
else else
{ {
// found a key that is not in target -> remove it // found a key that is not in target -> remove it
diff_remove(result, detail::concat<string_t>(current_path, '/', detail::escape(it.key()))); diff_remove(result, diff_key_path(current_path, it.key()));
} }
continue; continue;
} }
+1
View File
@@ -23,6 +23,7 @@
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/thirdparty/hedley/hedley.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
+714 -278
View File
File diff suppressed because it is too large. Load diff
+7 -7
View File
@@ -198,11 +198,11 @@ TEST_CASE("copy constructor and destructor across value types")
const json src_uint = 7u; const json src_uint = 7u;
const json src_float = 2.5; const json src_float = 2.5;
json c_null(src_null); json c_null(src_null); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
json c_bool(src_bool); json c_bool(src_bool); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
json c_int(src_int); json c_int(src_int); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
json c_uint(src_uint); json c_uint(src_uint); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
json c_float(src_float); json c_float(src_float); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
CHECK(c_null.is_null()); CHECK(c_null.is_null());
CHECK(c_bool == true); CHECK(c_bool == true);
@@ -300,12 +300,12 @@ TEST_CASE("copy constructor and destructor across value types")
REQUIRE(v.size() == 70); REQUIRE(v.size() == 70);
std::vector<json> copy(v); std::vector<json> copy(v);
CHECK(copy == v); CHECK((copy == v));
// mutating the copy leaves the original alone // mutating the copy leaves the original alone
copy[5] = "changed"; copy[5] = "changed";
copy[6].push_back(1); copy[6].push_back(1);
CHECK(copy != v); CHECK((copy != v));
CHECK(v[5] == std::string(5, 'x')); CHECK(v[5] == std::string(5, 'x'));
CHECK(v[6].size() == 3); CHECK(v[6].size() == 3);
+2 -2
View File
@@ -817,8 +817,8 @@ template<class Key, class T, class IgnoredLess = std::less<Key>,
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator> struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
{ {
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>; using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
using typename base_type::iterator; using iterator = typename base_type::iterator;
using typename base_type::Container; using Container = typename base_type::Container;
using base_type::base_type; using base_type::base_type;
template<typename... Args> template<typename... Args>
+5 -4
View File
@@ -1637,6 +1637,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
{ {
Json j = {1, 2, 3, 4}; Json j = {1, 2, 3, 4};
const std::vector<int> reversed = {4, 3, 2, 1};
auto it = j.end(); auto it = j.end();
std::vector<int> seen; std::vector<int> seen;
while (it != j.begin()) while (it != j.begin())
@@ -1644,7 +1645,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
--it; --it;
seen.push_back(it->template get<int>()); seen.push_back(it->template get<int>());
} }
CHECK(seen == std::vector<int> {4, 3, 2, 1}); CHECK(seen == reversed);
auto it2 = j.end(); auto it2 = j.end();
auto old = it2--; auto old = it2--;
@@ -1656,14 +1657,14 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
{ {
seen.push_back(r->template get<int>()); seen.push_back(r->template get<int>());
} }
CHECK(seen == std::vector<int> {4, 3, 2, 1}); CHECK(seen == reversed);
seen.clear(); seen.clear();
for (auto r = j.crbegin(); r != j.crend(); ++r) for (auto r = j.crbegin(); r != j.crend(); ++r)
{ {
seen.push_back(*r); seen.push_back(r->template get<int>());
} }
CHECK(seen == std::vector<int> {4, 3, 2, 1}); CHECK(seen == reversed);
} }
SECTION("object: -- and rbegin/rend") SECTION("object: -- and rbegin/rend")
+2 -2
View File
@@ -1230,11 +1230,11 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics")
SECTION("random tokens") SECTION("random tokens")
{ {
std::mt19937 gen(42); std::mt19937 gen(42); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex"; const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
std::string token(static_cast<std::size_t>(gen() % 23), '0'); std::string token(gen() % 23, '0');
for (auto& c : token) for (auto& c : token)
{ {
c = alphabet[gen() % alphabet.size()]; c = alphabet[gen() % alphabet.size()];
+8 -2
View File
@@ -1374,7 +1374,13 @@ void check_dump_against_reference(const std::string& s, const bool with_strict =
}; };
for (const auto error_handler : error_handlers) for (const auto error_handler : error_handlers)
{ {
#if defined(JSON_NOEXCEPTION)
// a strict dump of invalid UTF-8 calls std::abort() instead of throwing
static_cast<void>(with_strict);
if (error_handler == json::error_handler_t::strict)
#else
if (!with_strict && error_handler == json::error_handler_t::strict) if (!with_strict && error_handler == json::error_handler_t::strict)
#endif
{ {
continue; continue;
} }
@@ -1429,7 +1435,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
"\xF4\x90\x80\x80", "\xF4\x90\x80\x80",
// sequences cut short by ASCII, a control character, a quote, a // sequences cut short by ASCII, a control character, a quote, a
// backslash, DEL, or the lead byte of a well-formed character // backslash, DEL, or the lead byte of a well-formed character
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", "\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", // NOLINT(bugprone-suspicious-missing-comma): "\xC3" "a" ends the hex escape
// truncated sequences // truncated sequences
"\xC3", "\xE2\x82", "\xF0\x9F\x98" "\xC3", "\xE2\x82", "\xF0\x9F\x98"
}; };
@@ -1489,7 +1495,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
{ {
for (const auto& pattern : patterns) for (const auto& pattern : patterns)
{ {
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17 : 8)); check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17u : 8u));
} }
} }
} }
+4
View File
@@ -218,7 +218,11 @@ TEST_CASE("compliance tests from nativejson-benchmark")
n1e308[0] = '['; n1e308[0] = '[';
n1e308[1] = '1'; n1e308[1] = '1';
n1e308[310] = ']'; n1e308[310] = ']';
#if JSON_STRICT_NUL_HANDLING
n1e308.pop_back(); // a NUL byte after the value is a syntax error in strict mode
#else
n1e308[311] = '\0'; n1e308[311] = '\0';
#endif
TEST_DOUBLE(n1e308, 1E308); TEST_DOUBLE(n1e308, 1E308);
} }
+18 -9
View File
@@ -54,8 +54,8 @@ TEST_CASE("exception messages of out-of-line throw sites")
SECTION("at") SECTION("at")
{ {
const json carr = arr; const json& carr = arr;
const json cobj = obj; const json& cobj = obj;
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range"); CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range"); CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number"); CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
@@ -65,7 +65,7 @@ TEST_CASE("exception messages of out-of-line throw sites")
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number"); CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
nlohmann::ordered_json oobj = {{"a", 1}}; nlohmann::ordered_json oobj = {{"a", 1}};
const nlohmann::ordered_json coobj = oobj; const nlohmann::ordered_json& coobj = oobj;
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found"); CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
@@ -94,9 +94,10 @@ TEST_CASE("exception messages of out-of-line throw sites")
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
} }
#if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort()
SECTION("patch") SECTION("patch")
{ {
const auto patch_msg = [](const char* doc, const char* patch) const auto patch_msg = [](const char* doc, const char* patch) -> std::string
{ {
try try
{ {
@@ -105,16 +106,23 @@ TEST_CASE("exception messages of out-of-line throw sites")
} }
catch (const json::exception& e) catch (const json::exception& e)
{ {
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) "; drop it // with JSON_DIAGNOSTICS, what() names the offending value like "(/0) ",
// and with JSON_DIAGNOSTIC_POSITIONS its position like "(bytes 0-2) "; drop both
std::string msg = e.what(); std::string msg = e.what();
const auto pos = msg.find(" (/"); for (const char* marker :
if (pos != std::string::npos) {
" (/", " (bytes "
})
{ {
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1); const auto pos = msg.find(marker);
if (pos != std::string::npos)
{
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
}
} }
return msg; return msg;
} }
return std::string(); return {};
}; };
CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects"); CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
@@ -132,4 +140,5 @@ TEST_CASE("exception messages of out-of-line throw sites")
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}"); CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}");
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'"); CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
} }
#endif
} }