mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 14:08:13 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb7bd2dbc9 | ||
|
|
23f999547e | ||
|
|
adceb69a77 | ||
|
|
b0a7d5f6ff | ||
|
|
70fdb359f0 | ||
|
|
7717853976 | ||
|
|
bc94203dbc | ||
|
|
b85d1ca043 | ||
|
|
26b4716740 | ||
|
|
a3b9e9c712 | ||
|
|
985fa5cb2e | ||
|
|
f41f6408eb | ||
|
|
ff2d59d3f1 | ||
|
|
ceb3c92709 | ||
|
|
7241ba94c8 | ||
|
|
f9e2302af9 | ||
|
|
eb251df17c | ||
|
|
dd3ed1ef86 | ||
|
|
a517b8ea8b | ||
|
|
ecc2f511c7 | ||
|
|
4ee05e4d1f | ||
|
|
38536b6b4d | ||
|
|
7d3412a559 | ||
|
|
e82ee7375a | ||
|
|
ef2fee20d8 | ||
|
|
135095d692 |
No files matched your search
@@ -339,6 +339,11 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
||||
- `output_stream_adapter` writes to a `std::ostream`.
|
||||
- `output_string_adapter` appends to a string.
|
||||
|
||||
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
|
||||
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
|
||||
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
|
||||
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
|
||||
|
||||
## Value conversion
|
||||
|
||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memchr, memcmp, memcpy
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
@@ -303,12 +303,6 @@ class json_pointer
|
||||
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||
};
|
||||
|
||||
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
|
||||
static bool is_dash(const string_t& s) noexcept
|
||||
{
|
||||
return s.size() == 1 && s[0] == '-';
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] s reference token to be converted into an array index
|
||||
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
||||
@@ -334,7 +328,7 @@ class json_pointer
|
||||
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||
// token are left to the conversion below and are reported as unresolved
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
|
||||
{
|
||||
return array_index_status::not_a_number;
|
||||
}
|
||||
@@ -481,40 +475,9 @@ class json_pointer
|
||||
return pos == size;
|
||||
}
|
||||
|
||||
/// reserve room for @a n elements of an array type that has reserve()
|
||||
template<typename ArrayType>
|
||||
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(array.reserve(n), void())
|
||||
{
|
||||
array.reserve(n);
|
||||
}
|
||||
|
||||
template<typename ArrayType>
|
||||
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
/// @brief object member access with a key that is only copied if it is inserted
|
||||
///
|
||||
/// Objects that can find the position of a key and insert there
|
||||
/// (std::map) search once, others look the key up and add it if it is missing.
|
||||
template<typename BasicJsonType>
|
||||
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
auto it = members.lower_bound(key);
|
||||
if (it != members.end() && !members.key_comp()(key, it->first))
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto old_capacity = object.container_capacity();
|
||||
it = members.emplace_hint(it, key, nullptr);
|
||||
return object.set_parent_after_insert(it->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
const auto it = members.find(key);
|
||||
@@ -525,12 +488,6 @@ class json_pointer
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
return object_member_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
json_pointer top() const
|
||||
{
|
||||
@@ -763,103 +720,48 @@ class json_pointer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the values along the pointer last resolved by @ref resolve_plain
|
||||
|
||||
A patch from @ref basic_json::diff lists its operations in document order,
|
||||
so consecutive pointers share most of their reference tokens; the values
|
||||
along the previous pointer are kept to resume from the common prefix.
|
||||
`steps[k]` is the value after the first `k` reference tokens together with
|
||||
the length of the prefix of `*path` that holds them. A value stays valid as
|
||||
long as no operation changes its container: callers keep only values up to
|
||||
the parent of the pointer they modify, or call `reset()`.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
struct plain_steps
|
||||
{
|
||||
const string_t* path = nullptr;
|
||||
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
path = nullptr;
|
||||
steps.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief resolve a plain JSON pointer without building its reference tokens
|
||||
|
||||
@param[in] root the value the pointer starts at
|
||||
@param[in] ptr the value the pointer starts at
|
||||
@param[in] path a JSON pointer string
|
||||
@param[in] length the number of leading bytes of @a path to resolve; 0 or
|
||||
the position of a slash
|
||||
@param[in,out] token a string to hold the reference tokens, to reuse its storage
|
||||
@param[in,out] cache the values along the previously resolved pointer;
|
||||
@a path must outlive the next call
|
||||
|
||||
@return the pointed to value, or `nullptr` if the pointer does not denote
|
||||
an existing value, or does so in a way that is not plain: an array
|
||||
index that is malformed or out of range, or a "-", or a reference
|
||||
token with an escape. No exception is
|
||||
thrown for these; callers use @ref json_pointer instead, which
|
||||
handles them.
|
||||
an existing value, or does so in a way that is not plain: a
|
||||
reference token with an escape, an array index that is malformed or
|
||||
out of range, or a "-". No exception is thrown for these; callers
|
||||
use @ref json_pointer instead, which handles them.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
|
||||
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
|
||||
{
|
||||
auto& steps = cache.steps;
|
||||
|
||||
// resume from the deepest step that is also a prefix of this path
|
||||
std::size_t depth = 1;
|
||||
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
|
||||
{
|
||||
const string_t& previous = *cache.path;
|
||||
for (depth = steps.size(); depth > 1; --depth)
|
||||
{
|
||||
// a step ends at a token boundary of the previous path
|
||||
const std::size_t prefix = steps[depth - 1].first;
|
||||
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
steps.resize(depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
steps.assign(1, std::make_pair(std::size_t{0}, root));
|
||||
}
|
||||
cache.path = &path;
|
||||
|
||||
BasicJsonType* ptr = steps[depth - 1].second;
|
||||
for (std::size_t pos = steps[depth - 1].first; pos < length;)
|
||||
for (std::size_t pos = 0; pos < length;)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// the reference token between the slash at pos and the next one,
|
||||
// which must not have an escape
|
||||
const char* const begin = path.data() + pos + 1;
|
||||
const char* const limit = path.data() + length;
|
||||
const char* end = begin;
|
||||
while (end < limit && *end != '/')
|
||||
// the reference token between the slash at pos and the next one
|
||||
std::size_t end = path.find_first_of('/', pos + 1);
|
||||
if (end == string_t::npos || end > length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
++end;
|
||||
end = length;
|
||||
}
|
||||
token.clear();
|
||||
token.append(path.data() + pos + 1, end - pos - 1);
|
||||
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
switch (ptr->type())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
token.clear();
|
||||
token.append(begin, static_cast<std::size_t>(end - begin));
|
||||
const auto it = ptr->m_data.m_value.object->find(token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
@@ -871,24 +773,8 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
// an index of up to 18 digits without a leading zero fits into size_type
|
||||
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
|
||||
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
|
||||
const std::size_t digits = static_cast<std::size_t>(end - begin);
|
||||
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
typename BasicJsonType::size_type idx = 0;
|
||||
for (const char* c = begin; c != end; ++c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
typename BasicJsonType::size_type idx{};
|
||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -907,8 +793,7 @@ class json_pointer
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
pos = static_cast<std::size_t>(end - path.data());
|
||||
steps.emplace_back(pos, ptr);
|
||||
pos = end;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
@@ -1022,7 +907,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||
@@ -1093,7 +978,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" cannot be used for const access
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
@@ -1155,7 +1040,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
@@ -1232,7 +1117,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return false;
|
||||
@@ -1391,52 +1276,6 @@ class json_pointer
|
||||
obj[key] = std::forward<ValueType>(v);
|
||||
}
|
||||
|
||||
/// the number of entries that flattening @a value adds: its primitive
|
||||
/// values and empty containers
|
||||
template<typename BasicJsonType>
|
||||
static std::size_t count_flattened(const BasicJsonType& value)
|
||||
{
|
||||
std::size_t count = 0;
|
||||
std::vector<const BasicJsonType*> pending(1, &value);
|
||||
while (!pending.empty())
|
||||
{
|
||||
const BasicJsonType* const v = pending.back();
|
||||
pending.pop_back();
|
||||
if (v->is_array() && !v->m_data.m_value.array->empty())
|
||||
{
|
||||
for (const auto& element : *v->m_data.m_value.array)
|
||||
{
|
||||
pending.push_back(&element);
|
||||
}
|
||||
}
|
||||
else if (v->is_object() && !v->m_data.m_value.object->empty())
|
||||
{
|
||||
for (const auto& member : *v->m_data.m_value.object)
|
||||
{
|
||||
pending.push_back(&member.second);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Reserve the storage for @a value of objects that have a reserve(), so
|
||||
// that it does not have to be moved while it grows
|
||||
template<typename ObjectType, typename BasicJsonType>
|
||||
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(obj.reserve(std::size_t {}), void())
|
||||
{
|
||||
obj.reserve(count_flattened(value));
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename BasicJsonType>
|
||||
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
/// append the decimal representation of @a i to @a out
|
||||
static void append_index(string_t& out, std::size_t i)
|
||||
{
|
||||
@@ -1546,7 +1385,6 @@ class json_pointer
|
||||
}
|
||||
};
|
||||
|
||||
reserve_flattened(object, value, detail::priority_tag<1> {});
|
||||
enter(value);
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -1554,9 +1392,8 @@ class json_pointer
|
||||
// frame, which would invalidate a reference to it
|
||||
const BasicJsonType* const container = stack.back().container;
|
||||
|
||||
// the path of the previous child is replaced by the path of the
|
||||
// next one; for an array element in one resize
|
||||
const std::size_t path_length = stack.back().path_length;
|
||||
// drop the path of the previous child
|
||||
path.resize(stack.back().path_length);
|
||||
|
||||
if (container->is_array())
|
||||
{
|
||||
@@ -1570,20 +1407,8 @@ class json_pointer
|
||||
|
||||
// iterate array and use index as a reference string
|
||||
++stack.back().index;
|
||||
std::array<char, 24> digits{};
|
||||
std::size_t pos = digits.size();
|
||||
std::size_t rest = i;
|
||||
do
|
||||
{
|
||||
digits[--pos] = static_cast<char>('0' + rest % 10);
|
||||
rest /= 10;
|
||||
}
|
||||
while (rest != 0);
|
||||
const std::size_t count = digits.size() - pos;
|
||||
path.resize(path_length + 1 + count);
|
||||
char* const out = &path[path_length];
|
||||
out[0] = '/';
|
||||
std::memcpy(out + 1, digits.data() + pos, count);
|
||||
path.push_back('/');
|
||||
append_index(path, i);
|
||||
enter(array[i]);
|
||||
}
|
||||
else
|
||||
@@ -1597,7 +1422,6 @@ class json_pointer
|
||||
|
||||
// iterate object and use keys as reference string
|
||||
++stack.back().member;
|
||||
path.resize(path_length);
|
||||
path.push_back('/');
|
||||
detail::escape_into(path, it->first);
|
||||
enter(it->second);
|
||||
@@ -1693,28 +1517,8 @@ class json_pointer
|
||||
{
|
||||
end = key.size();
|
||||
}
|
||||
const std::size_t token_size = end - pos - 1;
|
||||
|
||||
// a token that is a plain index of up to 9 digits, which is
|
||||
// the case for almost all array elements, is not copied
|
||||
std::size_t index = 0;
|
||||
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
|
||||
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
|
||||
{
|
||||
plain_index = data[k] >= '0' && data[k] <= '9';
|
||||
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
|
||||
}
|
||||
if (plain_index)
|
||||
{
|
||||
current = ¤t->operator[](static_cast<typename BasicJsonType::size_type>(index));
|
||||
values.push_back(current);
|
||||
ends.push_back(end);
|
||||
pos = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
token.clear();
|
||||
token.append(data + pos + 1, token_size);
|
||||
token.append(data + pos + 1, end - pos - 1);
|
||||
if (token.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
detail::unescape(token);
|
||||
@@ -1724,13 +1528,10 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
// a first child 0 makes the value an array without a look-up
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
|
||||
if (arrays.contains(data, pos))
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
*current = BasicJsonType(detail::value_t::array);
|
||||
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
|
||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
}
|
||||
else
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -55,6 +55,66 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
/// output_vector_sink also appends writes of at least this size with insert()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief copy @a length characters, at most 32, without a call of memcpy()
|
||||
///
|
||||
/// Short strings are the typical payload of a binary value, and a call of
|
||||
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
|
||||
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
|
||||
/// so on down to a single character; each is a fixed-size load and store.
|
||||
/// Nothing outside of [src, src + length) is read or written.
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= 32);
|
||||
if (length >= 16)
|
||||
{
|
||||
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 16);
|
||||
std::memcpy(last.data(), src + (length - 16), 16);
|
||||
std::memcpy(dst, first.data(), 16);
|
||||
std::memcpy(dst + (length - 16), last.data(), 16);
|
||||
}
|
||||
else if (length >= 8)
|
||||
{
|
||||
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 8);
|
||||
std::memcpy(last.data(), src + (length - 8), 8);
|
||||
std::memcpy(dst, first.data(), 8);
|
||||
std::memcpy(dst + (length - 8), last.data(), 8);
|
||||
}
|
||||
else if (length >= 4)
|
||||
{
|
||||
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 4);
|
||||
std::memcpy(last.data(), src + (length - 4), 4);
|
||||
std::memcpy(dst, first.data(), 4);
|
||||
std::memcpy(dst + (length - 4), last.data(), 4);
|
||||
}
|
||||
else if (length != 0)
|
||||
{
|
||||
// 1 to 3: the first, the middle, and the last character
|
||||
const CharType a = src[0];
|
||||
const CharType b = src[length >> 1];
|
||||
const CharType c = src[length - 1];
|
||||
dst[0] = a;
|
||||
dst[length >> 1] = b;
|
||||
dst[length - 1] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief copy @a length characters, of a character type wider than a byte
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
|
||||
{
|
||||
std::memcpy(dst, src, length * sizeof(CharType));
|
||||
}
|
||||
|
||||
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
@@ -78,7 +138,7 @@ class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(&vec), position(vec.size())
|
||||
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||
{}
|
||||
|
||||
output_vector_sink(const output_vector_sink&) = delete;
|
||||
@@ -86,7 +146,7 @@ class output_vector_sink
|
||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||
|
||||
output_vector_sink(output_vector_sink&& other) noexcept
|
||||
: v(other.v), position(other.position)
|
||||
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||
{
|
||||
other.v = nullptr;
|
||||
}
|
||||
@@ -95,7 +155,7 @@ class output_vector_sink
|
||||
{
|
||||
if (v != nullptr)
|
||||
{
|
||||
v->resize(position);
|
||||
v->resize(position());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +164,11 @@ class output_vector_sink
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
||||
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
(*v)[position++] = c;
|
||||
*cursor++ = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
@@ -117,7 +177,7 @@ class output_vector_sink
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
{
|
||||
if (length >= max_reserve_size)
|
||||
{
|
||||
@@ -126,10 +186,15 @@ class output_vector_sink
|
||||
}
|
||||
grow(length);
|
||||
}
|
||||
if (length != 0)
|
||||
if (length <= 32)
|
||||
{
|
||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
||||
position += length;
|
||||
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
cursor += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||
cursor += length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,21 +209,37 @@ class output_vector_sink
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
return v->data() + position;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
position += length;
|
||||
cursor += length;
|
||||
}
|
||||
|
||||
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||
void flush() noexcept
|
||||
{}
|
||||
|
||||
private:
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(cursor - v->data());
|
||||
}
|
||||
|
||||
/// number of characters that fit the vector behind the write position
|
||||
std::size_t space() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(limit - cursor);
|
||||
}
|
||||
|
||||
/// make room for at least @a length more characters after the position;
|
||||
/// kept out of line so that the write functions stay small enough for
|
||||
/// GCC to inline them into the writer
|
||||
@@ -166,12 +247,15 @@ class output_vector_sink
|
||||
void grow(const std::size_t length)
|
||||
{
|
||||
// use the capacity the vector already has, else at least double it;
|
||||
// never less than 64 characters, so that a small value does not
|
||||
// never less than min_vector_sink_capacity, so that a small value does not
|
||||
// reallocate for every few bytes
|
||||
const std::size_t needed = position + length;
|
||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
||||
const std::size_t written = position();
|
||||
const std::size_t needed = written + length;
|
||||
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
cursor = v->data() + written;
|
||||
limit = v->data() + v->size();
|
||||
}
|
||||
|
||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||
@@ -181,20 +265,24 @@ class output_vector_sink
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void append(const CharType* s, const std::size_t length)
|
||||
{
|
||||
v->resize(position);
|
||||
if (v->capacity() - position < length)
|
||||
const std::size_t written = position();
|
||||
v->resize(written);
|
||||
if (v->capacity() - written < length)
|
||||
{
|
||||
// grow geometrically, with room for the writes that follow, so
|
||||
// that the next grow() need not reallocate right away
|
||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
||||
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||
}
|
||||
v->insert(v->end(), s, s + length);
|
||||
position = v->size();
|
||||
cursor = v->data() + v->size();
|
||||
limit = cursor;
|
||||
}
|
||||
|
||||
std::vector<CharType, AllocatorType>* v;
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position;
|
||||
/// the write position: in *v, at the number of characters written so far
|
||||
CharType* cursor;
|
||||
/// the end of *v
|
||||
CharType* limit;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
@@ -277,9 +365,17 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
/// adapters) via the `output_adapter`-based overloads.
|
||||
///
|
||||
/// A write through the adapter is a virtual call, and for a stream a call with
|
||||
/// a sentry on top, which costs more than encoding the few bytes of most values.
|
||||
/// The sink therefore collects what is written in a buffer and passes it on in
|
||||
/// one piece when it is full, a write is too large for it, or @ref flush is
|
||||
/// called. Everything written reaches the adapter in the same order and with
|
||||
/// the same bytes; the destructor passes on what is left, so that a write that
|
||||
/// throws leaves the adapter with exactly the bytes written before it. Only
|
||||
/// @ref flush can report an error of the adapter (the destructor must not
|
||||
/// throw), so it has to be called once the value is written.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
@@ -290,36 +386,121 @@ class output_adapter_sink
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
output_adapter_sink(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
|
||||
|
||||
output_adapter_sink(output_adapter_sink&& other) noexcept
|
||||
: oa(std::move(other.oa)), used(other.used)
|
||||
{
|
||||
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
|
||||
other.used = 0;
|
||||
}
|
||||
|
||||
~output_adapter_sink()
|
||||
{
|
||||
// a normal return has flushed already, so this is for a write that threw
|
||||
if (used != 0)
|
||||
{
|
||||
JSON_TRY
|
||||
{
|
||||
flush();
|
||||
}
|
||||
JSON_CATCH(...)
|
||||
{
|
||||
// nothing can be done about an adapter that throws here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
oa->write_character(c);
|
||||
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
buffer[used++] = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
write_large(s, length);
|
||||
return;
|
||||
}
|
||||
if (length <= 32)
|
||||
{
|
||||
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
used += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
|
||||
used += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
CharType* reserve(const std::size_t length) noexcept
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
static_cast<void>(length);
|
||||
return reserved.data();
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
return buffer.data() + used;
|
||||
}
|
||||
|
||||
/// write the first @a length characters written to the space from reserve()
|
||||
void commit(const std::size_t length)
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
oa->write_characters(reserved.data(), length);
|
||||
used += length;
|
||||
}
|
||||
|
||||
/// pass everything written so far on to the adapter
|
||||
void flush()
|
||||
{
|
||||
if (used != 0)
|
||||
{
|
||||
// reset first: the characters count as written even if the adapter throws
|
||||
const std::size_t length = used;
|
||||
used = 0;
|
||||
oa->write_characters(buffer.data(), length);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// a write that does not fit the space left in the buffer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_large(const CharType* s, const std::size_t length)
|
||||
{
|
||||
flush();
|
||||
if (length >= buffer_size)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data(), s, length * sizeof(CharType));
|
||||
used = length;
|
||||
}
|
||||
}
|
||||
|
||||
/// large enough for reserve() at any time
|
||||
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
|
||||
|
||||
output_adapter_t<CharType> oa;
|
||||
std::array<CharType, max_reserve_size> reserved{};
|
||||
/// number of characters of @ref buffer not yet passed on
|
||||
std::size_t used = 0;
|
||||
std::array<CharType, buffer_size> buffer{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
+74
-175
@@ -6321,7 +6321,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_cbor(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6331,7 +6331,9 @@ public:
|
||||
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a CBOR serialization of a given JSON value
|
||||
@@ -6339,7 +6341,9 @@ public:
|
||||
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_cbor(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6348,7 +6352,7 @@ public:
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_msgpack(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6358,7 +6362,9 @@ public:
|
||||
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6366,7 +6372,9 @@ public:
|
||||
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_msgpack(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6377,7 +6385,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
||||
return result;
|
||||
}
|
||||
@@ -6388,7 +6396,9 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6397,7 +6407,9 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6409,7 +6421,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
return result;
|
||||
}
|
||||
@@ -6421,7 +6433,9 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6431,7 +6445,9 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6440,7 +6456,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_bson(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6450,7 +6466,9 @@ public:
|
||||
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6458,7 +6476,9 @@ public:
|
||||
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_bson(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
@@ -6466,7 +6486,7 @@ public:
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result).write_bon8(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6475,14 +6495,18 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
||||
binary_writer<std::uint8_t> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||
{
|
||||
binary_writer<char>(o).write_bon8(j);
|
||||
binary_writer<char> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in CBOR format
|
||||
@@ -6878,72 +6902,6 @@ public:
|
||||
/// @name JSON Patch functions
|
||||
/// @{
|
||||
|
||||
private:
|
||||
/// the members of an operation of a JSON patch that are of interest
|
||||
struct patch_members
|
||||
{
|
||||
basic_json* op = nullptr;
|
||||
basic_json* path = nullptr;
|
||||
basic_json* value = nullptr;
|
||||
basic_json* from = nullptr;
|
||||
};
|
||||
|
||||
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
|
||||
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
|
||||
{
|
||||
patch_members members;
|
||||
for (auto& member : *val.m_data.m_value.object)
|
||||
{
|
||||
const string_t& key = member.first;
|
||||
switch (key.size())
|
||||
{
|
||||
case 2:
|
||||
if (key == "op")
|
||||
{
|
||||
members.op = &member.second;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (key == "path")
|
||||
{
|
||||
members.path = &member.second;
|
||||
}
|
||||
else if (key == "from")
|
||||
{
|
||||
members.from = &member.second;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (key == "value")
|
||||
{
|
||||
members.value = &member.second;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
/// @brief find the members by lookup for key types other than string_t
|
||||
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
|
||||
{
|
||||
const auto find_member = [&val](const char* name) -> basic_json *
|
||||
{
|
||||
const auto it = val.m_data.m_value.object->find(string_t(name));
|
||||
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
|
||||
};
|
||||
|
||||
patch_members members;
|
||||
members.op = find_member("op");
|
||||
members.path = find_member("path");
|
||||
members.value = find_member("value");
|
||||
members.from = find_member("from");
|
||||
return members;
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief applies a JSON patch in-place without copying the object
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||
void patch_inplace(const basic_json& json_patch)
|
||||
@@ -7117,18 +7075,18 @@ public:
|
||||
// a diff creates them: apply the operation and return true; return
|
||||
// false, with nothing changed, for all others
|
||||
string_t token;
|
||||
typename json_pointer::template plain_steps<basic_json> steps;
|
||||
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||
const string_t value_key = "value";
|
||||
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
|
||||
{
|
||||
if (path.empty())
|
||||
if (path.empty() || path.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// a missing "value" is reported below
|
||||
const basic_json* const value_ptr = members.value;
|
||||
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||
const auto value_it = val.m_data.m_value.object->find(value_key);
|
||||
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
|
||||
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -7137,7 +7095,7 @@ public:
|
||||
{
|
||||
case patch_operations::replace:
|
||||
{
|
||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
||||
if (target == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -7148,7 +7106,7 @@ public:
|
||||
|
||||
case patch_operations::test:
|
||||
{
|
||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
||||
// a failing test is reported below
|
||||
return target != nullptr && *target == value;
|
||||
}
|
||||
@@ -7168,17 +7126,13 @@ public:
|
||||
}
|
||||
--slash;
|
||||
|
||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
|
||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
|
||||
if (parent == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
token.clear();
|
||||
token.append(path.data() + slash + 1, path.size() - slash - 1);
|
||||
if (token.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (operation == patch_operations::add)
|
||||
{
|
||||
@@ -7234,32 +7188,31 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// the name of the first member to check
|
||||
const string_t op_member = "op";
|
||||
|
||||
// iterate and apply the operations
|
||||
for (const auto& val : json_patch)
|
||||
{
|
||||
// wrapper to get a value for an operation
|
||||
const auto get_value = [&val](basic_json * found,
|
||||
const string_t& op,
|
||||
const char* member,
|
||||
const auto get_value = [&val](const string_t& op,
|
||||
const string_t& member,
|
||||
bool string_type) -> basic_json &
|
||||
{
|
||||
// find value
|
||||
auto it = val.m_data.m_value.object->find(member);
|
||||
|
||||
// check if the desired value is present
|
||||
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
|
||||
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
|
||||
{
|
||||
detail::throw_patch_missing_member(op, string_t(member), false, val);
|
||||
detail::throw_patch_missing_member(op, member, false, val);
|
||||
}
|
||||
|
||||
// check if the result is of type string
|
||||
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
|
||||
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
|
||||
{
|
||||
detail::throw_patch_missing_member(op, string_t(member), true, val);
|
||||
detail::throw_patch_missing_member(op, member, true, val);
|
||||
}
|
||||
|
||||
// no error: return value
|
||||
return *found;
|
||||
return it->second;
|
||||
};
|
||||
|
||||
// type check: every element of the array must be an object
|
||||
@@ -7268,30 +7221,26 @@ public:
|
||||
detail::throw_patch_not_an_array(val);
|
||||
}
|
||||
|
||||
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
|
||||
|
||||
// collect mandatory members
|
||||
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
|
||||
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
||||
const patch_operations operation = get_op(op);
|
||||
|
||||
// the usual operations on a plain pointer are applied without
|
||||
// building the pointer's reference tokens; anything else (and
|
||||
// every error) is handled below
|
||||
if (try_plain(operation, path, members, val))
|
||||
if (try_plain(operation, path, val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// the general operations may change any container
|
||||
steps.reset();
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case patch_operations::add:
|
||||
{
|
||||
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||
operation_add(ptr, get_value("add", "value", false));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7304,13 +7253,13 @@ public:
|
||||
case patch_operations::replace:
|
||||
{
|
||||
// the "path" location must exist - use at()
|
||||
result.at(ptr) = get_value(members.value, "replace", "value", false);
|
||||
result.at(ptr) = get_value("replace", "value", false);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
|
||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||
@@ -7333,7 +7282,7 @@ public:
|
||||
|
||||
case patch_operations::copy:
|
||||
{
|
||||
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
|
||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
||||
const json_pointer from_ptr(from_path);
|
||||
|
||||
// The copy is functionally identical to an "add"
|
||||
@@ -7351,7 +7300,7 @@ public:
|
||||
{
|
||||
// check if "value" matches the one at "path"
|
||||
// the "path" location must exist - use at()
|
||||
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
|
||||
success = (result.at(ptr) == get_value("test", "value", false));
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
@@ -7443,18 +7392,13 @@ public:
|
||||
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
|
||||
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
|
||||
{
|
||||
// the keys are distinct and in the order of std::map; the members are
|
||||
// added without looking them up
|
||||
basic_json entry(value_t::object);
|
||||
auto& members = *entry.m_data.m_value.object;
|
||||
typename object_t::iterator hint = members.end();
|
||||
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
|
||||
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
|
||||
entry["op"] = op;
|
||||
entry["path"] = path;
|
||||
if (value != nullptr)
|
||||
{
|
||||
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
|
||||
entry["value"] = *value;
|
||||
}
|
||||
entry.set_parents();
|
||||
result.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
@@ -7476,50 +7420,6 @@ public:
|
||||
diff_operation(result, "add", path, &value);
|
||||
}
|
||||
|
||||
/// @brief the values in the target of the members of a source object, for the
|
||||
/// objects of up to 16 members without allocating
|
||||
///
|
||||
/// reserve() must be called with the number of values that are added.
|
||||
class diff_target_values
|
||||
{
|
||||
public:
|
||||
diff_target_values() = default;
|
||||
diff_target_values(const diff_target_values&) = delete;
|
||||
diff_target_values& operator=(const diff_target_values&) = delete;
|
||||
~diff_target_values() = default;
|
||||
|
||||
void reserve(const std::size_t n)
|
||||
{
|
||||
if (n > inline_size)
|
||||
{
|
||||
large.resize(n);
|
||||
items = large.data();
|
||||
}
|
||||
}
|
||||
|
||||
void push_back(const basic_json* value)
|
||||
{
|
||||
items[count++] = value;
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
const basic_json* operator[](const std::size_t i) const noexcept
|
||||
{
|
||||
return items[i];
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t inline_size = 16;
|
||||
std::size_t count = 0;
|
||||
const basic_json** items = inline_items;
|
||||
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
|
||||
const basic_json* inline_items[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
||||
};
|
||||
|
||||
/// @brief append "/" and the decimal representation of @a index to @a path
|
||||
static void diff_append_index(string_t& path, std::size_t index)
|
||||
{
|
||||
@@ -7600,9 +7500,8 @@ public:
|
||||
operations that remove every member of @a source and add every member of
|
||||
@a target to @a result, and returns false.
|
||||
*/
|
||||
template<typename TargetValues>
|
||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, TargetValues& target_values,
|
||||
const string_t& path, std::vector<const basic_json*>& target_values,
|
||||
std::vector<const_iterator>& added)
|
||||
{
|
||||
// first pass: one lookup per source key
|
||||
@@ -7816,7 +7715,7 @@ public:
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
diff_target_values target_values;
|
||||
std::vector<const basic_json*> target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||
{
|
||||
|
||||
Generated
+2040
-609
File diff suppressed because it is too large.
Load diff
@@ -492,3 +492,275 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// numbers around every boundary of the integer encodings, and floats of each
|
||||
// kind (exact in float, needing double, special values)
|
||||
std::vector<json> boundary_numbers()
|
||||
{
|
||||
std::vector<json> numbers;
|
||||
for (int k = 0; k < 64; ++k)
|
||||
{
|
||||
const std::uint64_t p = std::uint64_t{1} << k;
|
||||
for (const std::uint64_t d :
|
||||
{
|
||||
p - 1, p, p + 1
|
||||
})
|
||||
{
|
||||
numbers.emplace_back(d);
|
||||
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
numbers.emplace_back(static_cast<std::int64_t>(d));
|
||||
numbers.emplace_back(-static_cast<std::int64_t>(d));
|
||||
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
|
||||
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
|
||||
for (const double f :
|
||||
{
|
||||
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
|
||||
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN()
|
||||
})
|
||||
{
|
||||
numbers.emplace_back(f);
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
|
||||
template<typename Single>
|
||||
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
|
||||
{
|
||||
std::vector<std::uint8_t> bytes;
|
||||
for (std::size_t i = first; i < first + count; ++i)
|
||||
{
|
||||
const auto one = single(numbers[i]);
|
||||
bytes.insert(bytes.end(), one.begin(), one.end());
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Runs of numbers in arrays are encoded straight into space reserved from the
|
||||
// sink, by code that is separate from the encoding of a single value. Both have
|
||||
// to agree on every number: an array is its head and the encodings of its
|
||||
// elements, one after the other.
|
||||
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
|
||||
{
|
||||
const std::vector<json> numbers = boundary_numbers();
|
||||
|
||||
// arrays of every length that a one-byte head covers, so that runs are as
|
||||
// long as, and longer and shorter than, what fits one reservation
|
||||
for (const std::size_t count :
|
||||
{
|
||||
1u, 2u, 3u, 5u, 14u, 15u, 23u
|
||||
})
|
||||
{
|
||||
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
|
||||
{
|
||||
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
|
||||
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
|
||||
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||
|
||||
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
|
||||
auto elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_cbor(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
CHECK(json::to_cbor(array) == expected);
|
||||
|
||||
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
|
||||
if (count > 15)
|
||||
{
|
||||
expected.push_back(0);
|
||||
expected.push_back(static_cast<std::uint8_t>(count));
|
||||
}
|
||||
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_msgpack(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
CHECK(json::to_msgpack(array) == expected);
|
||||
|
||||
for (const bool bjdata :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
expected.assign(1, static_cast<std::uint8_t>('['));
|
||||
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
|
||||
{
|
||||
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
expected.push_back(static_cast<std::uint8_t>(']'));
|
||||
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range
|
||||
bool representable = true;
|
||||
for (std::size_t i = first; i < first + count; ++i)
|
||||
{
|
||||
representable = representable && bon8_representable(numbers[i]);
|
||||
}
|
||||
if (representable)
|
||||
{
|
||||
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
|
||||
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_bon8(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
if (count > 4)
|
||||
{
|
||||
expected.push_back(0xFE);
|
||||
}
|
||||
CHECK(json::to_bon8(array) == expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An array that is written in more than one reservation (more numbers than fit
|
||||
// one), and arrays of an index with more digits, round trip for every format.
|
||||
TEST_CASE("large arrays and many digits of indices round trip")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 12000; ++i)
|
||||
{
|
||||
switch (i % 4)
|
||||
{
|
||||
case 0:
|
||||
array.push_back(i);
|
||||
break;
|
||||
case 1:
|
||||
array.push_back(-i);
|
||||
break;
|
||||
case 2:
|
||||
array.push_back(i * 0.25);
|
||||
break;
|
||||
default:
|
||||
array.push_back(i % 8 == 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
json document = json::object();
|
||||
document["numbers"] = array;
|
||||
document["nested"] = json::array({array, "text", array});
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||
}
|
||||
|
||||
// Strings and keys are written with their head in one reservation up to a
|
||||
// length; around that length, and with every kind of head, they must read back.
|
||||
TEST_CASE("strings of every head length round trip")
|
||||
{
|
||||
json document = json::object();
|
||||
json array = json::array();
|
||||
for (const std::size_t length :
|
||||
{
|
||||
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
|
||||
})
|
||||
{
|
||||
const std::string text(length, 'x');
|
||||
array.push_back(text);
|
||||
document[std::string(length, 'k')] = text;
|
||||
}
|
||||
document["array"] = array;
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||
}
|
||||
|
||||
// What a write that throws leaves behind in an output adapter is what was written
|
||||
// before it, whatever the adapter and however the adapter sink buffers.
|
||||
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 700; ++i)
|
||||
{
|
||||
array.push_back(i * 1000);
|
||||
array.push_back("text " + std::to_string(i));
|
||||
}
|
||||
array.push_back("bad \xFF byte");
|
||||
json document = json::object();
|
||||
document["first"] = array;
|
||||
document["second"] = 1;
|
||||
|
||||
const auto strict = json::error_handler_t::strict;
|
||||
|
||||
SECTION("cbor")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("msgpack")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("ubjson")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("bson")
|
||||
{
|
||||
// the size pass finds the string before anything is written
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
|
||||
CHECK(as_vector.empty());
|
||||
CHECK(as_string.empty());
|
||||
CHECK(as_stream.str().empty());
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,55 @@ TEST_CASE("custom number types: integers beyond 64 bits")
|
||||
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
||||
}
|
||||
|
||||
SECTION("integers in arrays and objects")
|
||||
{
|
||||
// runs of numbers and scalar object members are encoded by loops of
|
||||
// their own for the number types up to 64 bits; a wider number type
|
||||
// takes the generic path, which checks the range of every integer
|
||||
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
|
||||
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
|
||||
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
|
||||
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
|
||||
|
||||
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
|
||||
for (const auto& value : failing)
|
||||
{
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
|
||||
}
|
||||
|
||||
// a failing write leaves no more than the elements before the failing one
|
||||
std::vector<std::uint8_t> out;
|
||||
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
|
||||
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
|
||||
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
|
||||
out.clear();
|
||||
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
|
||||
CHECK(!out.empty());
|
||||
|
||||
// the same values that fit are written exactly as with 64-bit number types
|
||||
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
|
||||
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
|
||||
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
|
||||
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
|
||||
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
|
||||
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
|
||||
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
|
||||
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
|
||||
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
|
||||
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
|
||||
|
||||
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
|
||||
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
|
||||
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
|
||||
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
|
||||
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
|
||||
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
|
||||
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
||||
}
|
||||
|
||||
SECTION("BJData ND-array")
|
||||
{
|
||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||
|
||||
Reference in new issue
Block a user