mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 19:18:12 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2241af3297 | ||
|
|
f941cf7a7e | ||
|
|
994099ca28 | ||
|
|
80407e8316 | ||
|
|
72c45349c9 | ||
|
|
7ed54e3d59 | ||
|
|
5f2d976129 | ||
|
|
cd6b428ef0 |
No files matched your search
@@ -166,19 +166,21 @@ jobs:
|
||||
# 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.
|
||||
# Keep the objects small by splitting the test files instead.
|
||||
# Objects with more than 65535 sections fail to link with "relocation
|
||||
# truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'": the MinGW
|
||||
# linker stores the section an associative COMDAT section belongs to in
|
||||
# 16 bits (x_associated in binutils' include/coff/internal.h), so it
|
||||
# discards the jump tables of inline functions together with the wrong
|
||||
# function. Each basic_json specialization a test file instantiates adds
|
||||
# many sections, so test a second one in a file of its own.
|
||||
# The tests link with LLD: GNU ld fails on objects with more than 32767
|
||||
# sections with "relocation truncated to fit: IMAGE_REL_AMD64_REL32
|
||||
# against `.rdata'", as it stores the section an associative COMDAT
|
||||
# section belongs to in 16 bits (x_associated in binutils'
|
||||
# include/coff/internal.h) and so discards the jump tables of inline
|
||||
# functions together with the wrong function. Each basic_json
|
||||
# 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
|
||||
run: cmake -S . -B build ^
|
||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
|
||||
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld -lwinpthread" ^
|
||||
-G"MinGW Makefiles" ^
|
||||
-DCMAKE_BUILD_TYPE=Debug ^
|
||||
-DJSON_BuildTests=On
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <optional> // optional
|
||||
#endif
|
||||
|
||||
#include <iterator> // begin, end
|
||||
#include <iterator> // begin, end, make_move_iterator, move_iterator
|
||||
#include <memory> // allocator_traits
|
||||
#include <string> // basic_string, char_traits
|
||||
#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.
|
||||
*/
|
||||
|
||||
// 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<>
|
||||
@@ -174,7 +200,7 @@ struct external_constructor<value_t::array>
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -235,7 +261,7 @@ struct external_constructor<value_t::object>
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -413,25 +439,27 @@ using is_movable_container_rvalue = negation<disjunction<
|
||||
std::is_const<T>,
|
||||
has_dedicated_to_json<T>,
|
||||
is_basic_json<T>,
|
||||
is_range_view_type<T>,
|
||||
is_enum_keyed_map_type<T>,
|
||||
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>
|
||||
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>,
|
||||
negation<disjunction<
|
||||
std::is_same<T, typename BasicJsonType::array_t>,
|
||||
std::is_same<T, typename BasicJsonType::binary_t>,
|
||||
is_compatible_object_type<BasicJsonType, T>,
|
||||
is_compatible_string_type<BasicJsonType, T>,
|
||||
is_compatible_binary_type<BasicJsonType, T>>>>;
|
||||
|
||||
// rvalue containers: their elements are moved instead of copied
|
||||
template<typename BasicJsonType, typename CompatibleArrayType,
|
||||
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||
{
|
||||
external_constructor<value_t::array>::construct_moving(j, arr);
|
||||
}
|
||||
@@ -509,7 +537,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
||||
}
|
||||
#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>
|
||||
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<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,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
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)
|
||||
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;
|
||||
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
|
||||
std::uint64_t digit = 0;
|
||||
|
||||
@@ -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>(val);
|
||||
// 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(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||
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));
|
||||
}
|
||||
|
||||
template<typename StringType, typename BasicJsonType>
|
||||
|
||||
@@ -680,12 +680,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -9,9 +9,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // all_of
|
||||
#include <array> // array
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
@@ -334,7 +338,7 @@ class json_pointer
|
||||
{
|
||||
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))
|
||||
{
|
||||
idx = acc;
|
||||
@@ -491,50 +495,111 @@ class json_pointer
|
||||
|
||||
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
|
||||
among their children; @ref get_and_create creates arrays exactly below
|
||||
those prefixes and objects everywhere else. Deciding this up front keeps
|
||||
the result independent of the order in which the flattened object is
|
||||
iterated, which is unspecified for some object types.
|
||||
@ref unflatten creates arrays exactly below the prefixes that have a
|
||||
reference token 0 among their children, and objects everywhere else.
|
||||
Deciding this up front keeps the result independent of the order in which
|
||||
the flattened object is iterated, which is unspecified for some object
|
||||
types.
|
||||
|
||||
The prefixes form a tree and are numbered (number 0 is the empty prefix),
|
||||
so each of them is stored only once (as a node) rather than as a copy of
|
||||
all of its reference tokens. The tree does not own the reference tokens:
|
||||
it refers to the tokens of the parsed keys, which must outlive it.
|
||||
A prefix is identified by the text of the key it is the beginning of:
|
||||
escaping is unambiguous, so equal prefixes of valid keys are equal texts.
|
||||
The set refers to the keys, which must outlive it.
|
||||
*/
|
||||
struct prefix_tree
|
||||
class array_prefix_set
|
||||
{
|
||||
// open-addressing hash table from (parent, last reference token) to
|
||||
// the number of the node; empty slots have token == nullptr
|
||||
public:
|
||||
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
|
||||
{
|
||||
std::size_t hash;
|
||||
std::size_t parent;
|
||||
const string_t* token;
|
||||
std::size_t id;
|
||||
slot() noexcept = default;
|
||||
|
||||
slot() noexcept
|
||||
: hash(0), parent(0), token(nullptr), id(0)
|
||||
slot(std::size_t h, const char* d, std::size_t s) noexcept
|
||||
: hash(h), data(d), size(s)
|
||||
{}
|
||||
|
||||
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept
|
||||
: hash(h), parent(p), token(t), id(i)
|
||||
{}
|
||||
std::size_t hash = 0;
|
||||
const char* data = nullptr; // nullptr: the slot is empty
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
std::vector<slot> slots;
|
||||
std::size_t used = 0;
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
static std::size_t hash_of(const char* data, std::size_t size) noexcept
|
||||
{
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||
for (std::size_t i = 0; i < token.size(); ++i)
|
||||
std::uint64_t h = 0x9E3779B97F4A7C15ULL ^ size;
|
||||
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()
|
||||
@@ -544,10 +609,10 @@ class json_pointer
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.token != nullptr)
|
||||
if (s.data != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
while (slots[i].data != nullptr)
|
||||
{
|
||||
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<std::size_t> parent;
|
||||
// is_array[id] is true iff some flattened key has the reference token
|
||||
// 0 directly below the prefix with number id
|
||||
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;
|
||||
}
|
||||
std::vector<slot> slots{};
|
||||
std::size_t used = 0;
|
||||
const char* last_data = nullptr;
|
||||
std::size_t last_size = 0;
|
||||
};
|
||||
|
||||
/// @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
|
||||
@param[in] tokens the reference tokens of the pointer
|
||||
@param[in] prefixes prefixes[i] is the number in @a tree of the prefix
|
||||
consisting of the first i reference tokens
|
||||
@throw parse_error.107 if the key is not empty or begins with '/'
|
||||
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
||||
|
||||
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'
|
||||
@throw parse_error.109 if array index is not a number
|
||||
@throw type_error.313 if value cannot be unflattened
|
||||
const char* const data = key.data();
|
||||
if (JSON_HEDLEY_UNLIKELY(data[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", 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>
|
||||
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens,
|
||||
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
|
||||
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
|
||||
{
|
||||
auto* result = &j;
|
||||
|
||||
// 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)
|
||||
for (std::size_t pos = 0; pos < length;)
|
||||
{
|
||||
const auto& reference_token = tokens[i];
|
||||
switch (result->type())
|
||||
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
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;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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:
|
||||
{
|
||||
// create an entry in the object
|
||||
result = &object_member(*result, reference_token);
|
||||
const auto it = ptr->m_data.m_value.object->find(token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
// create an entry in the array
|
||||
result = &result->operator[](array_index<BasicJsonType>(reference_token));
|
||||
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;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
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::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
@@ -675,11 +783,12 @@ class json_pointer
|
||||
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", &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
|
||||
// distinct, so objects that can append a key known to be absent
|
||||
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||
// quadratic; all other object types use operator[].
|
||||
// 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>
|
||||
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())
|
||||
{
|
||||
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename ValueType>
|
||||
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, 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);
|
||||
}
|
||||
|
||||
/// 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] value the value to consider
|
||||
@@ -1193,7 +1327,8 @@ class json_pointer
|
||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||
// are added to the result right away; other containers get a frame
|
||||
auto& object = *result.m_data.m_value.object;
|
||||
const auto enter = [&stack, &path, &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())
|
||||
{
|
||||
@@ -1202,7 +1337,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.array->empty())
|
||||
{
|
||||
// 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
|
||||
{
|
||||
@@ -1216,7 +1351,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.object->empty())
|
||||
{
|
||||
// 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
|
||||
{
|
||||
@@ -1236,7 +1371,7 @@ class json_pointer
|
||||
default:
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1264,7 +1399,8 @@ class json_pointer
|
||||
|
||||
// iterate array and use index as a reference string
|
||||
++stack.back().index;
|
||||
detail::concat_into(path, '/', detail::to_string<string_t>(i));
|
||||
path.push_back('/');
|
||||
append_index(path, i);
|
||||
enter(array[i]);
|
||||
}
|
||||
else
|
||||
@@ -1278,7 +1414,8 @@ class json_pointer
|
||||
|
||||
// iterate object and use keys as reference string
|
||||
++stack.back().member;
|
||||
detail::concat_into(path, '/', detail::escape(it->first));
|
||||
path.push_back('/');
|
||||
detail::escape_into(path, it->first);
|
||||
enter(it->second);
|
||||
}
|
||||
}
|
||||
@@ -1309,53 +1446,30 @@ class json_pointer
|
||||
|
||||
BasicJsonType result;
|
||||
|
||||
// parse every key once; the reference tokens are kept for the second
|
||||
// pass (the vectors are never resized, so the tree may refer to them)
|
||||
std::vector<std::vector<string_t>> keys;
|
||||
keys.reserve(value.m_data.m_value.object->size());
|
||||
// check every key, and collect the prefixes that have a reference
|
||||
// token 0 among their children; the values below them are arrays,
|
||||
// all others are objects (see array_prefix_set)
|
||||
array_prefix_set arrays;
|
||||
const string_t* previous_key = nullptr;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
json_pointer ptr(element.first);
|
||||
keys.push_back(std::move(ptr.reference_tokens));
|
||||
scan_key(element.first, previous_key, arrays);
|
||||
previous_key = &element.first;
|
||||
}
|
||||
|
||||
// collect the pointer prefixes that have a reference token 0 among
|
||||
// their children; the values below them are arrays, all others are
|
||||
// objects (see prefix_tree)
|
||||
prefix_tree tree;
|
||||
// the number of the prefix with i + 1 tokens of the previous key; keys
|
||||
// are often sorted, so most of the prefix is the same as for the
|
||||
// previous key and need not be looked up again
|
||||
std::vector<std::size_t> path;
|
||||
const std::vector<string_t>* previous = nullptr;
|
||||
// the number of the complete prefix (i.e., the key) of each key
|
||||
std::vector<std::size_t> leaves;
|
||||
leaves.reserve(keys.size());
|
||||
for (const auto& tokens : keys)
|
||||
{
|
||||
std::size_t common = 0;
|
||||
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;
|
||||
}
|
||||
// values[i] is the value after the first i reference tokens of the
|
||||
// previous key, ends[i] the length of that prefix of the key. Keys are
|
||||
// often sorted, so most of the path to the value of a key is the
|
||||
// same as for the previous key and need not be looked up again. Only
|
||||
// the values below the common prefix are dropped, and only they can
|
||||
// move when a value is added: the ones above are not inside the
|
||||
// container that grows.
|
||||
std::vector<BasicJsonType*> values(1, &result);
|
||||
std::vector<std::size_t> ends(1, 0);
|
||||
const string_t* previous = nullptr;
|
||||
string_t token;
|
||||
|
||||
// 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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
const auto& tokens = keys[key_index];
|
||||
// number the prefixes of this key from its last one to the first
|
||||
prefixes.resize(tokens.size());
|
||||
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i)
|
||||
const string_t& key = element.first;
|
||||
const char* const data = key.data();
|
||||
|
||||
// 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];
|
||||
prefixes[i - 1] = id;
|
||||
const std::size_t same = common_prefix_length(data, previous->data(), (std::min)(key.size(), previous->size()));
|
||||
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
|
||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||
// function get_and_create returns a reference to the result itself.
|
||||
// An assignment will then create a primitive value.
|
||||
get_and_create(result, tokens, prefixes, tree) = element.second;
|
||||
// the value is the result itself. An assignment will then create a
|
||||
// primitive value.
|
||||
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 = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
}
|
||||
else
|
||||
{
|
||||
// start a new object otherwise
|
||||
current = ¤t->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 = ¤t->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;
|
||||
|
||||
+292
-79
@@ -29,6 +29,7 @@
|
||||
#endif // JSON_NO_IO
|
||||
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <set> // swap, operator!=
|
||||
#include <stdexcept> // length_error
|
||||
@@ -754,26 +755,20 @@ private:
|
||||
// one level deep); stops at the first structured child it meets
|
||||
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)
|
||||
{
|
||||
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 false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return has_no_children(el);
|
||||
});
|
||||
}
|
||||
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 false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return has_no_children(el.second);
|
||||
});
|
||||
}
|
||||
|
||||
// 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 >
|
||||
static constexpr std::size_t object_capacity() noexcept
|
||||
std::size_t object_capacity() const noexcept
|
||||
{
|
||||
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 >
|
||||
static constexpr std::size_t object_capacity() noexcept
|
||||
std::size_t object_capacity() const noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
}
|
||||
@@ -3043,18 +3038,21 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
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_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
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_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
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")));
|
||||
}
|
||||
|
||||
@@ -3062,6 +3060,8 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -3070,6 +3070,8 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -4893,19 +4895,19 @@ public:
|
||||
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -4913,21 +4915,21 @@ public:
|
||||
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -4935,19 +4937,19 @@ public:
|
||||
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -5453,7 +5455,7 @@ public:
|
||||
// inline fast path for two values of the same non-owning scalar type; anything
|
||||
// else falls through to the out-of-line general comparison
|
||||
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
|
||||
if (lhs.m_data.m_type == rhs.m_data.m_type) \
|
||||
if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \
|
||||
{ \
|
||||
switch (lhs.m_data.m_type) \
|
||||
{ \
|
||||
@@ -7064,6 +7066,125 @@ public:
|
||||
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
|
||||
for (const auto& val : json_patch)
|
||||
{
|
||||
@@ -7100,9 +7221,19 @@ public:
|
||||
// collect mandatory members
|
||||
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, val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (get_op(op))
|
||||
switch (operation)
|
||||
{
|
||||
case patch_operations::add:
|
||||
{
|
||||
@@ -7210,7 +7341,8 @@ public:
|
||||
{
|
||||
// the patch
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -7254,31 +7386,69 @@ public:
|
||||
// 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.
|
||||
|
||||
/// @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
|
||||
static void diff_replace(basic_json& result, const string_t& path, const basic_json& value)
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"}, {"path", path}, {"value", value}
|
||||
});
|
||||
diff_operation(result, "replace", path, &value);
|
||||
}
|
||||
|
||||
/// @brief append a "remove" operation for @a path to @a result
|
||||
static void diff_remove(basic_json& result, const string_t& path)
|
||||
{
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path}
|
||||
}));
|
||||
diff_operation(result, "remove", path, nullptr);
|
||||
}
|
||||
|
||||
/// @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)
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "add"}, {"path", path}, {"value", value}
|
||||
});
|
||||
diff_operation(result, "add", path, &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
|
||||
@@ -7291,13 +7461,18 @@ public:
|
||||
// in that order avoids the quadratic reinsertion done before
|
||||
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
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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());
|
||||
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());
|
||||
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
|
||||
// keeps its members in insertion order, such as the one backing
|
||||
// ordered_json, also needs the common keys to be in the same order
|
||||
@@ -7408,6 +7604,13 @@ public:
|
||||
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
|
||||
// order in source and target (only possible for a
|
||||
// reorderable object_t like ordered_map). Building a
|
||||
@@ -7424,7 +7627,7 @@ public:
|
||||
// it moves it to the end, fixing its position.
|
||||
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
|
||||
@@ -7433,7 +7636,7 @@ public:
|
||||
// target exactly
|
||||
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;
|
||||
}
|
||||
@@ -7442,6 +7645,9 @@ public:
|
||||
@brief @ref diff, for values at nesting level @a depth, appending the
|
||||
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
|
||||
level, so values nested deeply enough used to exhaust the call stack and
|
||||
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.
|
||||
*/
|
||||
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 (source == target)
|
||||
@@ -7461,9 +7667,9 @@ public:
|
||||
}
|
||||
|
||||
/// @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,
|
||||
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()))
|
||||
{
|
||||
@@ -7482,6 +7688,8 @@ public:
|
||||
{
|
||||
case value_t::array:
|
||||
{
|
||||
const std::size_t path_length = path.size();
|
||||
|
||||
// first pass: traverse common elements
|
||||
std::size_t i = 0;
|
||||
while (i < source.size() && i < target.size())
|
||||
@@ -7489,7 +7697,9 @@ public:
|
||||
// recursive call to compare array values at index 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;
|
||||
}
|
||||
@@ -7511,6 +7721,7 @@ public:
|
||||
// lookup is needed. Deleted keys are interleaved here
|
||||
// too, in source's original order, to match the
|
||||
// historical (pre-reordering-aware) output order.
|
||||
const std::size_t path_length = path.size();
|
||||
std::size_t i = 0;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
|
||||
{
|
||||
@@ -7518,13 +7729,15 @@ public:
|
||||
{
|
||||
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
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
++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]);
|
||||
if (stack.size() == depth)
|
||||
{
|
||||
@@ -7678,7 +7891,7 @@ public:
|
||||
if (target_value_ptr != nullptr)
|
||||
{
|
||||
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);
|
||||
if (stack.size() == depth)
|
||||
{
|
||||
@@ -7688,7 +7901,7 @@ public:
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
Generated
+714
-278
File diff suppressed because it is too large.
Load diff
@@ -198,11 +198,11 @@ TEST_CASE("copy constructor and destructor across value types")
|
||||
const json src_uint = 7u;
|
||||
const json src_float = 2.5;
|
||||
|
||||
json c_null(src_null);
|
||||
json c_bool(src_bool);
|
||||
json c_int(src_int);
|
||||
json c_uint(src_uint);
|
||||
json c_float(src_float);
|
||||
json c_null(src_null); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_bool(src_bool); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_int(src_int); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_uint(src_uint); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_float(src_float); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
|
||||
CHECK(c_null.is_null());
|
||||
CHECK(c_bool == true);
|
||||
@@ -300,12 +300,12 @@ TEST_CASE("copy constructor and destructor across value types")
|
||||
REQUIRE(v.size() == 70);
|
||||
|
||||
std::vector<json> copy(v);
|
||||
CHECK(copy == v);
|
||||
CHECK((copy == v));
|
||||
|
||||
// mutating the copy leaves the original alone
|
||||
copy[5] = "changed";
|
||||
copy[6].push_back(1);
|
||||
CHECK(copy != v);
|
||||
CHECK((copy != v));
|
||||
CHECK(v[5] == std::string(5, 'x'));
|
||||
CHECK(v[6].size() == 3);
|
||||
|
||||
|
||||
@@ -817,8 +817,8 @@ template<class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
|
||||
{
|
||||
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
|
||||
using typename base_type::iterator;
|
||||
using typename base_type::Container;
|
||||
using iterator = typename base_type::iterator;
|
||||
using Container = typename base_type::Container;
|
||||
using base_type::base_type;
|
||||
|
||||
template<typename... Args>
|
||||
|
||||
@@ -1637,6 +1637,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
||||
{
|
||||
Json j = {1, 2, 3, 4};
|
||||
|
||||
const std::vector<int> reversed = {4, 3, 2, 1};
|
||||
auto it = j.end();
|
||||
std::vector<int> seen;
|
||||
while (it != j.begin())
|
||||
@@ -1644,7 +1645,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
||||
--it;
|
||||
seen.push_back(it->template get<int>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
CHECK(seen == reversed);
|
||||
|
||||
auto it2 = j.end();
|
||||
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>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
CHECK(seen == reversed);
|
||||
|
||||
seen.clear();
|
||||
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")
|
||||
|
||||
@@ -1230,11 +1230,11 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics")
|
||||
|
||||
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";
|
||||
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)
|
||||
{
|
||||
c = alphabet[gen() % alphabet.size()];
|
||||
|
||||
@@ -1374,7 +1374,13 @@ void check_dump_against_reference(const std::string& s, const bool with_strict =
|
||||
};
|
||||
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)
|
||||
#endif
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1429,7 +1435,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||
"\xF4\x90\x80\x80",
|
||||
// sequences cut short by ASCII, a control character, a quote, a
|
||||
// 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
|
||||
"\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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,11 @@ TEST_CASE("compliance tests from nativejson-benchmark")
|
||||
n1e308[0] = '[';
|
||||
n1e308[1] = '1';
|
||||
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';
|
||||
#endif
|
||||
TEST_DOUBLE(n1e308, 1E308);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
|
||||
SECTION("at")
|
||||
{
|
||||
const json carr = arr;
|
||||
const json cobj = obj;
|
||||
const json& carr = arr;
|
||||
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)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");
|
||||
@@ -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");
|
||||
|
||||
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)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");
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort()
|
||||
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
|
||||
{
|
||||
@@ -105,16 +106,23 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
}
|
||||
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();
|
||||
const auto pos = msg.find(" (/");
|
||||
if (pos != std::string::npos)
|
||||
for (const char* marker :
|
||||
{
|
||||
" (/", " (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 std::string();
|
||||
return {};
|
||||
};
|
||||
|
||||
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\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user