mirror of
https://github.com/nlohmann/json.git
synced 2026-10-17 19:48:12 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65fbbc5588 | ||
|
|
8c2857d974 | ||
|
|
007aaaf113 | ||
|
|
8d61fb8ca2 | ||
|
|
278a73bd20 | ||
|
|
ba9448e007 | ||
|
|
6baf56a239 | ||
|
|
f3ae7f618e |
No files matched your search
@@ -9,10 +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 <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
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||||
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
#include <nlohmann/detail/string_escape.hpp>
|
#include <nlohmann/detail/string_escape.hpp>
|
||||||
#include <nlohmann/detail/string_utils.hpp>
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
@@ -492,50 +496,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::numeric_limits<std::size_t>::max)());
|
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::numeric_limits<std::size_t>::max)());
|
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 detail::conditional_static_cast<std::size_t>(h ^ (h >> 29u));
|
||||||
}
|
}
|
||||||
|
|
||||||
void grow()
|
void grow()
|
||||||
@@ -545,10 +610,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;
|
||||||
}
|
}
|
||||||
@@ -557,117 +622,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:
|
||||||
@@ -676,11 +784,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1135,20 +1244,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
|
||||||
@@ -1194,7 +1328,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)
|
auto hint = object.end();
|
||||||
|
const auto enter = [&stack, &path, &object, &hint](const BasicJsonType & v)
|
||||||
{
|
{
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -1203,7 +1338,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
|
||||||
{
|
{
|
||||||
@@ -1217,7 +1352,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
|
||||||
{
|
{
|
||||||
@@ -1237,7 +1372,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1265,7 +1400,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
|
||||||
@@ -1279,7 +1415,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1310,53 +1447,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()))
|
||||||
@@ -1364,21 +1478,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 = ¤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;
|
return result;
|
||||||
|
|||||||
+241
-30
@@ -7066,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)
|
||||||
{
|
{
|
||||||
@@ -7102,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:
|
||||||
{
|
{
|
||||||
@@ -7212,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7256,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
|
||||||
@@ -7293,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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7308,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7329,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())
|
||||||
{
|
{
|
||||||
@@ -7345,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
|
||||||
@@ -7410,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
|
||||||
@@ -7426,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
|
||||||
@@ -7435,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;
|
||||||
}
|
}
|
||||||
@@ -7444,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
|
||||||
@@ -7451,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)
|
||||||
@@ -7463,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()))
|
||||||
{
|
{
|
||||||
@@ -7484,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())
|
||||||
@@ -7491,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;
|
||||||
}
|
}
|
||||||
@@ -7513,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)
|
||||||
{
|
{
|
||||||
@@ -7520,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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7657,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)
|
||||||
{
|
{
|
||||||
@@ -7680,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)
|
||||||
{
|
{
|
||||||
@@ -7690,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;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+618
-213
File diff suppressed because it is too large.
Load diff
@@ -1,503 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// The fast paths for contiguous input (bulk string scanning, SWAR searches,
|
|
||||||
// UTF-8 prefix validation, the 8-digit number loop, the 8-byte indentation
|
|
||||||
// skip, the BSON/BON8 bulk string readers) load several bytes at a time. This
|
|
||||||
// test places every input directly in front of (or directly behind) an
|
|
||||||
// inaccessible page, so reading even one byte outside the input crashes, with
|
|
||||||
// or without sanitizers. ASan alone misses over-reads into the slack of the
|
|
||||||
// same heap allocation.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#define JSON_TEST_GUARD_PAGE_WINDOWS
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX
|
|
||||||
#endif
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#elif defined(__unix__) || defined(__APPLE__)
|
|
||||||
#define JSON_TEST_GUARD_PAGE_POSIX
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS) || defined(JSON_TEST_GUARD_PAGE_POSIX)
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// Three pages: an inaccessible one, a readable one, an inaccessible one. An
|
|
||||||
// input is copied either to the end of the middle page (any read past its last
|
|
||||||
// byte faults) or to its start (any read before its first byte faults).
|
|
||||||
class guarded_buffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
guarded_buffer()
|
|
||||||
: page(page_size())
|
|
||||||
{
|
|
||||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
|
||||||
void* const p = VirtualAlloc(nullptr, 3 * page, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
|
|
||||||
REQUIRE(p != nullptr);
|
|
||||||
base = static_cast<unsigned char*>(p);
|
|
||||||
DWORD old_protect = 0;
|
|
||||||
REQUIRE(VirtualProtect(base + page, page, PAGE_READWRITE, &old_protect) != 0);
|
|
||||||
#else
|
|
||||||
void* const p = mmap(nullptr, 3 * page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
|
||||||
REQUIRE(p != MAP_FAILED);
|
|
||||||
base = static_cast<unsigned char*>(p);
|
|
||||||
REQUIRE(mprotect(base + page, page, PROT_READ | PROT_WRITE) == 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
~guarded_buffer()
|
|
||||||
{
|
|
||||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
|
||||||
VirtualFree(base, 0, MEM_RELEASE);
|
|
||||||
#else
|
|
||||||
munmap(base, 3 * page);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
guarded_buffer(const guarded_buffer&) = delete;
|
|
||||||
guarded_buffer& operator=(const guarded_buffer&) = delete;
|
|
||||||
guarded_buffer(guarded_buffer&&) = delete;
|
|
||||||
guarded_buffer& operator=(guarded_buffer&&) = delete;
|
|
||||||
|
|
||||||
// copies @a input so its last byte is the last readable byte
|
|
||||||
const std::uint8_t* at_end(const std::string& input)
|
|
||||||
{
|
|
||||||
REQUIRE(input.size() <= page);
|
|
||||||
unsigned char* const first = base + (2 * page) - input.size();
|
|
||||||
std::transform(input.begin(), input.end(), first, [](const char c)
|
|
||||||
{
|
|
||||||
return static_cast<unsigned char>(c);
|
|
||||||
});
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copies @a input so its first byte is the first readable byte
|
|
||||||
const std::uint8_t* at_start(const std::string& input)
|
|
||||||
{
|
|
||||||
REQUIRE(input.size() <= page);
|
|
||||||
unsigned char* const first = base + page;
|
|
||||||
std::transform(input.begin(), input.end(), first, [](const char c)
|
|
||||||
{
|
|
||||||
return static_cast<unsigned char>(c);
|
|
||||||
});
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::size_t page_size()
|
|
||||||
{
|
|
||||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
|
||||||
SYSTEM_INFO info;
|
|
||||||
GetSystemInfo(&info);
|
|
||||||
return static_cast<std::size_t>(info.dwPageSize);
|
|
||||||
#else
|
|
||||||
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t page = 0;
|
|
||||||
unsigned char* base = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
// the serialization of the result of @a f, or the exception message; @a f
|
|
||||||
// takes the allow_exceptions flag, which is false without exceptions, where
|
|
||||||
// an error yields a discarded value instead
|
|
||||||
template<typename F>
|
|
||||||
std::string outcome(F f)
|
|
||||||
{
|
|
||||||
#if defined(JSON_NOEXCEPTION)
|
|
||||||
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
|
|
||||||
#else
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
return std::string("error: ") + e.what();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// a SAX handler that only counts events, so sax_parse runs without a DOM
|
|
||||||
struct counting_sax : nlohmann::json_sax<json>
|
|
||||||
{
|
|
||||||
std::size_t events = 0;
|
|
||||||
std::string error{}; // NOLINT(readability-redundant-member-init)
|
|
||||||
|
|
||||||
bool null() override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool boolean(bool /*val*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool number_integer(number_integer_t /*val*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool string(string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool binary(binary_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool start_object(std::size_t /*elements*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool key(string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool end_object() override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool start_array(std::size_t /*elements*/) override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool end_array() override
|
|
||||||
{
|
|
||||||
return count();
|
|
||||||
}
|
|
||||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const nlohmann::detail::exception& ex) override
|
|
||||||
{
|
|
||||||
error = std::to_string(position) + " " + ex.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool count()
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// all prefixes of each seed, so the input ends at every byte of it
|
|
||||||
std::vector<std::string> all_prefixes(const std::vector<std::string>& seeds)
|
|
||||||
{
|
|
||||||
std::vector<std::string> result;
|
|
||||||
for (const auto& seed : seeds)
|
|
||||||
{
|
|
||||||
for (std::size_t n = 0; n <= seed.size(); ++n)
|
|
||||||
{
|
|
||||||
result.push_back(seed.substr(0, n));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> json_seeds()
|
|
||||||
{
|
|
||||||
std::vector<std::string> seeds =
|
|
||||||
{
|
|
||||||
// string runs: ASCII, escapes, \u escapes, surrogate pairs
|
|
||||||
"\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"",
|
|
||||||
R"("ab\ncd\"ef\\gh\u00e4ij\uD83D\uDE00kl\u12")",
|
|
||||||
// 2-, 3- and 4-byte UTF-8 sequences, then invalid UTF-8
|
|
||||||
std::string("\"\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80") + "abcdefgh\"",
|
|
||||||
"\"abcdefgh\xC3\x28 \xED\xA0\x80 \xF4\x90\x80\x80 \xF8 \xC0\xAF\"",
|
|
||||||
std::string("\"abcdefg\x01") + "abcdefgh\"",
|
|
||||||
// numbers, so the 8-digit loop runs up to the end
|
|
||||||
"12345678",
|
|
||||||
"1234567890123456",
|
|
||||||
"12345678901234567890123",
|
|
||||||
"-0.000000001234567890123456789",
|
|
||||||
"3.14159265358979323846264338327950288e+100",
|
|
||||||
"-1.2345678901234567e-12",
|
|
||||||
"1E-5",
|
|
||||||
"[1.5,2e10,-3E-5,0.000000001,123456789012345678]",
|
|
||||||
// literals
|
|
||||||
"[true,false,null]",
|
|
||||||
// comments (only parsed with ignore_comments)
|
|
||||||
"/* comment */ [1, // line comment\n 2] /**/",
|
|
||||||
// a pretty-printed document
|
|
||||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(4),
|
|
||||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(8, '\t'),
|
|
||||||
};
|
|
||||||
|
|
||||||
// indentation runs of every length, with and without a closing bracket
|
|
||||||
for (std::size_t n = 0; n <= 20; ++n)
|
|
||||||
{
|
|
||||||
seeds.push_back("[\n" + std::string(n, ' ') + "1,\n" + std::string(n, ' ') + "2\n]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return all_prefixes(seeds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// a value whose binary serializations contain long strings and keys, numbers
|
|
||||||
// of every width, and nested containers
|
|
||||||
json binary_document()
|
|
||||||
{
|
|
||||||
json j;
|
|
||||||
j["a key that is longer than eight bytes"] = "a string value that is longer than eight bytes";
|
|
||||||
j["\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80"] = "\xF0\x9F\x98\x80\xE2\x82\xAC\xC3\xA4 utf-8 0123456789";
|
|
||||||
j["numbers"] = {0, 1, -1, 255, -256, 65536, -2147483649LL, 4294967296ULL, 1.5, -2.25e100};
|
|
||||||
j["nested"] = {{"x", {{"y", {true, false, nullptr}}}}};
|
|
||||||
j["s"] = "";
|
|
||||||
return j;
|
|
||||||
}
|
|
||||||
|
|
||||||
// readers for check_binary(), callable with a container or an iterator range
|
|
||||||
// followed by the allow_exceptions flag
|
|
||||||
#define JSON_TEST_GUARD_PAGE_READER(name, from) \
|
|
||||||
struct name \
|
|
||||||
{ \
|
|
||||||
template<typename... Args> \
|
|
||||||
json operator()(bool allow_exceptions, Args&& ... args) const \
|
|
||||||
{ \
|
|
||||||
return json::from(std::forward<Args>(args)..., true, allow_exceptions); \
|
|
||||||
} \
|
|
||||||
};
|
|
||||||
JSON_TEST_GUARD_PAGE_READER(read_bson, from_bson)
|
|
||||||
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
|
|
||||||
JSON_TEST_GUARD_PAGE_READER(read_cbor, from_cbor)
|
|
||||||
JSON_TEST_GUARD_PAGE_READER(read_msgpack, from_msgpack)
|
|
||||||
JSON_TEST_GUARD_PAGE_READER(read_ubjson, from_ubjson)
|
|
||||||
JSON_TEST_GUARD_PAGE_READER(read_bjdata, from_bjdata)
|
|
||||||
#undef JSON_TEST_GUARD_PAGE_READER
|
|
||||||
|
|
||||||
// @a bytes as characters (explicitly converted: the iterator-range constructor
|
|
||||||
// would convert implicitly, which -fsanitize=integer reports)
|
|
||||||
std::string as_chars(const std::vector<std::uint8_t>& bytes)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
result.reserve(bytes.size());
|
|
||||||
for (const std::uint8_t b : bytes)
|
|
||||||
{
|
|
||||||
result.push_back(static_cast<char>(b));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reads every prefix of @a bytes at both ends of the guarded page and from a
|
|
||||||
// std::string, which must give the same outcome
|
|
||||||
template<typename Reader>
|
|
||||||
void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes, Reader parse)
|
|
||||||
{
|
|
||||||
for (const auto& input : all_prefixes({as_chars(bytes)}))
|
|
||||||
{
|
|
||||||
CAPTURE(input)
|
|
||||||
const std::string expected = outcome([&](bool allow)
|
|
||||||
{
|
|
||||||
return parse(allow, input);
|
|
||||||
});
|
|
||||||
for (int placement = 0; placement < 2; ++placement)
|
|
||||||
{
|
|
||||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
|
||||||
CHECK(outcome([&](bool allow)
|
|
||||||
{
|
|
||||||
return parse(allow, first, first + input.size());
|
|
||||||
}) == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse_float_native() at both ends of the guarded page, compared bit for bit
|
|
||||||
// with converting the same token from a std::string
|
|
||||||
template<typename FloatType>
|
|
||||||
std::uint64_t float_bits(FloatType value)
|
|
||||||
{
|
|
||||||
static_assert(sizeof(FloatType) <= sizeof(std::uint64_t), "unsupported floating-point width");
|
|
||||||
std::uint64_t bits = 0;
|
|
||||||
std::memcpy(&bits, &value, sizeof(value));
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FloatType>
|
|
||||||
void check_float_native(guarded_buffer& guard, const std::string& token)
|
|
||||||
{
|
|
||||||
const auto decimal_point = token.find('.');
|
|
||||||
const auto exponent = token.find_first_of("eE");
|
|
||||||
const auto mantissa_end = exponent == std::string::npos ? token.size() : exponent;
|
|
||||||
const auto expected = nlohmann::detail::parse_float_native<FloatType>(token.data(), token.data() + token.size(), decimal_point, mantissa_end);
|
|
||||||
for (int placement = 0; placement < 2; ++placement)
|
|
||||||
{
|
|
||||||
const char* const first = reinterpret_cast<const char*>(placement == 0 ? guard.at_end(token) : guard.at_start(token));
|
|
||||||
const auto value = nlohmann::detail::parse_float_native<FloatType>(first, first + token.size(), decimal_point, mantissa_end);
|
|
||||||
CHECK(float_bits(value) == float_bits(expected));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("guard page")
|
|
||||||
{
|
|
||||||
guarded_buffer guard;
|
|
||||||
|
|
||||||
SECTION("string scanning")
|
|
||||||
{
|
|
||||||
// fills that drive each function to a different stop
|
|
||||||
const std::vector<std::string> fills =
|
|
||||||
{
|
|
||||||
std::string(64, 'a'),
|
|
||||||
std::string(64, '"'),
|
|
||||||
std::string(64, '\\'),
|
|
||||||
std::string(64, '\x01'),
|
|
||||||
std::string(64, '\x7F'),
|
|
||||||
std::string(64, '\xFF'),
|
|
||||||
};
|
|
||||||
std::vector<std::string> patterns = fills;
|
|
||||||
{
|
|
||||||
std::string utf8;
|
|
||||||
while (utf8.size() < 64)
|
|
||||||
{
|
|
||||||
utf8 += "\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80" "ab";
|
|
||||||
}
|
|
||||||
patterns.push_back(utf8);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::string bytes;
|
|
||||||
for (unsigned int i = 0; i < 64; ++i)
|
|
||||||
{
|
|
||||||
bytes.push_back(static_cast<char>((i * 37u) & 0xFFu));
|
|
||||||
}
|
|
||||||
patterns.push_back(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& pattern : patterns)
|
|
||||||
{
|
|
||||||
for (std::size_t n = 0; n <= 64; ++n)
|
|
||||||
{
|
|
||||||
const std::string input = pattern.substr(0, n);
|
|
||||||
for (int placement = 0; placement < 2; ++placement)
|
|
||||||
{
|
|
||||||
const unsigned char* const p = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
|
||||||
|
|
||||||
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
|
|
||||||
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
|
|
||||||
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
|
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
|
||||||
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
|
|
||||||
#endif
|
|
||||||
CHECK(nlohmann::detail::string_bulk_run(p, n) <= n);
|
|
||||||
CHECK(nlohmann::detail::scalar_string_bulk_run(p, n) <= n);
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
CHECK(nlohmann::detail::validate_one_utf8(p + i, n - i) <= n - i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("float conversion")
|
|
||||||
{
|
|
||||||
// the lexer converts floats from its token buffer, so the JSON section
|
|
||||||
// below never places a float token at the page boundary; this calls the
|
|
||||||
// conversion directly: the 8-digit loops of parse_float_significand()
|
|
||||||
// and digit_comparison() read up to the end of the token
|
|
||||||
const std::vector<std::string> tokens = all_prefixes(
|
|
||||||
{
|
|
||||||
"1234567812345678123456781234567812345678",
|
|
||||||
"-1.2345678901234567890123456789e-123",
|
|
||||||
"0.00000000000000000000000000000000000001",
|
|
||||||
"98765432.10987654",
|
|
||||||
"123456789012345678901234567890e-30",
|
|
||||||
// halfway between 1 and the next double, then just above it
|
|
||||||
"1.00000000000000011102230246251565404236316680908203125",
|
|
||||||
"1.000000000000000111022302462515654042363166809082031250000000001",
|
|
||||||
});
|
|
||||||
for (const auto& token : tokens)
|
|
||||||
{
|
|
||||||
// only complete number tokens: the conversion expects validated input
|
|
||||||
if (!token.empty() && json::accept(token))
|
|
||||||
{
|
|
||||||
CAPTURE(token)
|
|
||||||
check_float_native<double>(guard, token);
|
|
||||||
check_float_native<float>(guard, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("JSON")
|
|
||||||
{
|
|
||||||
for (const auto& input : json_seeds())
|
|
||||||
{
|
|
||||||
CAPTURE(input)
|
|
||||||
const std::string expected = outcome([&](bool allow)
|
|
||||||
{
|
|
||||||
return json::parse(input, nullptr, allow);
|
|
||||||
});
|
|
||||||
const std::string expected_comments = outcome([&](bool allow)
|
|
||||||
{
|
|
||||||
return json::parse(input, nullptr, allow, true);
|
|
||||||
});
|
|
||||||
const bool expected_accept = json::accept(input);
|
|
||||||
counting_sax expected_sax;
|
|
||||||
const bool expected_sax_result = json::sax_parse(input, &expected_sax);
|
|
||||||
|
|
||||||
for (int placement = 0; placement < 2; ++placement)
|
|
||||||
{
|
|
||||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
|
||||||
const std::uint8_t* const last = first + input.size();
|
|
||||||
|
|
||||||
CHECK(outcome([&](bool allow)
|
|
||||||
{
|
|
||||||
return json::parse(first, last, nullptr, allow);
|
|
||||||
}) == expected);
|
|
||||||
CHECK(outcome([&](bool allow)
|
|
||||||
{
|
|
||||||
return json::parse(first, last, nullptr, allow, true);
|
|
||||||
}) == expected_comments);
|
|
||||||
CHECK(json::accept(first, last) == expected_accept);
|
|
||||||
|
|
||||||
counting_sax sax;
|
|
||||||
CHECK(json::sax_parse(first, last, &sax) == expected_sax_result);
|
|
||||||
CHECK(sax.events == expected_sax.events);
|
|
||||||
CHECK(sax.error == expected_sax.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("binary formats")
|
|
||||||
{
|
|
||||||
// every prefix ends inside a value, e.g., inside a BSON key or BON8
|
|
||||||
// string without its terminator
|
|
||||||
const json j = binary_document();
|
|
||||||
check_binary(guard, json::to_bson(j), read_bson());
|
|
||||||
check_binary(guard, json::to_bon8(j), read_bon8());
|
|
||||||
check_binary(guard, json::to_cbor(j), read_cbor());
|
|
||||||
check_binary(guard, json::to_msgpack(j), read_msgpack());
|
|
||||||
check_binary(guard, json::to_ubjson(j), read_ubjson());
|
|
||||||
check_binary(guard, json::to_bjdata(j), read_bjdata());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Reference in new issue
Block a user