Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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_stream_adapter` writes to a `std::ostream`.
|
||||||
- `output_string_adapter` appends to a string.
|
- `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
|
## Value conversion
|
||||||
|
|
||||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
|
|||||||
/*!
|
/*!
|
||||||
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_binary_parser<BasicJsonType, InputAdapterType>>
|
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
|
||||||
class binary_reader
|
class binary_reader
|
||||||
{
|
{
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -14,7 +14,6 @@
|
|||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcmp
|
#include <cstring> // memcmp
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <limits> // numeric_limits
|
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -1817,15 +1816,6 @@ scan_number_done:
|
|||||||
/// significand (see scan_number_bulk_contiguous())
|
/// significand (see scan_number_bulk_contiguous())
|
||||||
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||||
{
|
{
|
||||||
// accept() only needs to know that the value is finite (see the parser):
|
|
||||||
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
|
||||||
// value that underflows is a finite number as well. Only a token that
|
|
||||||
// may overflow has to be converted to find out.
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
|
||||||
{
|
|
||||||
value_float = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
value_float = decimal_to_float<number_float_t>(s);
|
value_float = decimal_to_float<number_float_t>(s);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,13 +250,6 @@ class parser
|
|||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
// a document that starts with a container pushes onto the stack at
|
|
||||||
// once: allocate it in one step instead of growing it from 1 byte
|
|
||||||
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
|
|
||||||
{
|
|
||||||
states.reserve(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (!skip_to_state_evaluation)
|
if (!skip_to_state_evaluation)
|
||||||
|
|||||||
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()
|
/// output_vector_sink also appends writes of at least this size with insert()
|
||||||
constexpr std::size_t max_reserve_size = 256;
|
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
|
/// @brief non-virtual output sink writing into a std::vector
|
||||||
///
|
///
|
||||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||||
@@ -78,7 +138,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
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;
|
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& operator=(output_vector_sink&&) = delete;
|
||||||
|
|
||||||
output_vector_sink(output_vector_sink&& other) noexcept
|
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;
|
other.v = nullptr;
|
||||||
}
|
}
|
||||||
@@ -95,7 +155,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
if (v != nullptr)
|
if (v != nullptr)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
v->resize(position());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +164,11 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_character(CharType c)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||||
{
|
{
|
||||||
grow(1);
|
grow(1);
|
||||||
}
|
}
|
||||||
(*v)[position++] = c;
|
*cursor++ = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||||
@@ -117,7 +177,7 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
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)
|
if (length >= max_reserve_size)
|
||||||
{
|
{
|
||||||
@@ -126,10 +186,15 @@ class output_vector_sink
|
|||||||
}
|
}
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
if (length != 0)
|
if (length <= 32)
|
||||||
{
|
{
|
||||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||||
position += length;
|
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)
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
return v->data() + position;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// keep the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void commit(const std::size_t length) noexcept
|
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:
|
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;
|
/// 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
|
/// kept out of line so that the write functions stay small enough for
|
||||||
/// GCC to inline them into the writer
|
/// GCC to inline them into the writer
|
||||||
@@ -166,12 +247,15 @@ class output_vector_sink
|
|||||||
void grow(const std::size_t length)
|
void grow(const std::size_t length)
|
||||||
{
|
{
|
||||||
// use the capacity the vector already has, else at least double it;
|
// 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
|
// reallocate for every few bytes
|
||||||
const std::size_t needed = position + length;
|
const std::size_t written = position();
|
||||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
const std::size_t needed = written + length;
|
||||||
|
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||||
? v->capacity()
|
? v->capacity()
|
||||||
: (std::max)(needed, v->capacity() * 2)));
|
: (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
|
/// 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
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
void append(const CharType* s, const std::size_t length)
|
void append(const CharType* s, const std::size_t length)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
const std::size_t written = position();
|
||||||
if (v->capacity() - position < length)
|
v->resize(written);
|
||||||
|
if (v->capacity() - written < length)
|
||||||
{
|
{
|
||||||
// grow geometrically, with room for the writes that follow, so
|
// grow geometrically, with room for the writes that follow, so
|
||||||
// that the next grow() need not reallocate right away
|
// 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);
|
v->insert(v->end(), s, s + length);
|
||||||
position = v->size();
|
cursor = v->data() + v->size();
|
||||||
|
limit = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CharType, AllocatorType>* v;
|
std::vector<CharType, AllocatorType>* v;
|
||||||
/// number of characters of *v written so far
|
/// the write position: in *v, at the number of characters written so far
|
||||||
std::size_t position;
|
CharType* cursor;
|
||||||
|
/// the end of *v
|
||||||
|
CharType* limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// output adapter for byte vectors
|
/// 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
|
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
/// adapters) via the `output_adapter`-based overloads.
|
||||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
///
|
||||||
/// avoid it.
|
/// 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>
|
template<typename CharType>
|
||||||
class output_adapter_sink
|
class output_adapter_sink
|
||||||
{
|
{
|
||||||
@@ -290,36 +386,121 @@ class output_adapter_sink
|
|||||||
JSON_ASSERT(oa);
|
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)
|
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
|
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||||
// the type-erased path already did before this sink existed
|
// the type-erased path already did before this sink existed
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
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
|
/// space for writing up to @a length characters (see
|
||||||
/// output_vector_sink::reserve)
|
/// 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);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
static_cast<void>(length);
|
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||||
return reserved.data();
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
return buffer.data() + used;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// write the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
void commit(const std::size_t length)
|
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:
|
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;
|
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
|
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||||
|
|||||||
+44
-22
@@ -161,8 +161,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
|
||||||
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
friend struct ::nlohmann::detail::diagnostic_positions;
|
friend struct ::nlohmann::detail::diagnostic_positions;
|
||||||
@@ -6289,7 +6287,7 @@ public:
|
|||||||
from_ubjson/from_bjdata/from_bon8/from_bson overloads
|
from_ubjson/from_bjdata/from_bon8/from_bson overloads
|
||||||
|
|
||||||
Building the @ref binary_reader as a named local, rather than as a
|
Building the @ref binary_reader as a named local, rather than as a
|
||||||
temporary that @ref detail::json_sax_dom_binary_parser::parse is called on in
|
temporary that @ref detail::json_sax_dom_parser::parse is called on in
|
||||||
the same expression, avoids a false-positive cppcheck accessMoved
|
the same expression, avoids a false-positive cppcheck accessMoved
|
||||||
warning in each of the 16 callers.
|
warning in each of the 16 callers.
|
||||||
*/
|
*/
|
||||||
@@ -6300,7 +6298,7 @@ public:
|
|||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
detail::json_sax_dom_binary_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
|
||||||
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
|
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
|
||||||
if (!reader.sax_parse(&sdp, strict, tag_handler))
|
if (!reader.sax_parse(&sdp, strict, tag_handler))
|
||||||
{
|
{
|
||||||
@@ -6323,7 +6321,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
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);
|
vector_writer(result, error_handler).write_cbor(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6333,7 +6331,9 @@ public:
|
|||||||
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
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())
|
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
|
/// @brief create a CBOR serialization of a given JSON value
|
||||||
@@ -6341,7 +6341,9 @@ public:
|
|||||||
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
|
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())
|
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
|
/// @brief create a MessagePack serialization of a given JSON value
|
||||||
@@ -6350,7 +6352,7 @@ public:
|
|||||||
const error_handler_t error_handler = error_handler_t::keep)
|
const error_handler_t error_handler = error_handler_t::keep)
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
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);
|
vector_writer(result, error_handler).write_msgpack(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6360,7 +6362,9 @@ public:
|
|||||||
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
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)
|
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
|
/// @brief create a MessagePack serialization of a given JSON value
|
||||||
@@ -6368,7 +6372,9 @@ public:
|
|||||||
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
||||||
const error_handler_t error_handler = error_handler_t::keep)
|
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
|
/// @brief create a UBJSON serialization of a given JSON value
|
||||||
@@ -6379,7 +6385,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
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);
|
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6390,7 +6396,9 @@ public:
|
|||||||
const bool use_size = false, const bool use_type = false,
|
const bool use_size = false, const bool use_type = false,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
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
|
/// @brief create a UBJSON serialization of a given JSON value
|
||||||
@@ -6399,7 +6407,9 @@ public:
|
|||||||
const bool use_size = false, const bool use_type = false,
|
const bool use_size = false, const bool use_type = false,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
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
|
/// @brief create a BJData serialization of a given JSON value
|
||||||
@@ -6411,7 +6421,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
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);
|
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6423,7 +6433,9 @@ public:
|
|||||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
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
|
/// @brief create a BJData serialization of a given JSON value
|
||||||
@@ -6433,7 +6445,9 @@ public:
|
|||||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
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
|
/// @brief create a BSON serialization of a given JSON value
|
||||||
@@ -6442,7 +6456,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
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);
|
vector_writer(result, error_handler).write_bson(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6452,7 +6466,9 @@ public:
|
|||||||
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
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())
|
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
|
/// @brief create a BSON serialization of a given JSON value
|
||||||
@@ -6460,7 +6476,9 @@ public:
|
|||||||
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
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())
|
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
|
/// @brief create a BON8 serialization of a given JSON value
|
||||||
@@ -6468,7 +6486,7 @@ public:
|
|||||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
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);
|
vector_writer(result).write_bon8(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6477,14 +6495,18 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
/// @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)
|
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
|
/// @brief create a BON8 serialization of a given JSON value
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
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
|
/// @brief create a JSON value from an input in CBOR format
|
||||||
|
|||||||
Generated
+2029
-1304
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);
|
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(as_vector.begin(), as_vector.end()) == as_string);
|
||||||
|
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(as_vector.begin(), as_vector.end()) == as_string);
|
||||||
|
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(as_vector.begin(), as_vector.end()) == as_string);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2952,32 +2952,3 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
|
|
||||||
{
|
|
||||||
// accept() does not convert a float that cannot overflow; the tokens
|
|
||||||
// around the limit (and beyond it) must still be rejected exactly as
|
|
||||||
// parse() rejects them
|
|
||||||
const char* const numbers[] =
|
|
||||||
{
|
|
||||||
"1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309",
|
|
||||||
"10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309",
|
|
||||||
"1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308",
|
|
||||||
"12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291",
|
|
||||||
"1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290",
|
|
||||||
"0.00000000000000000001e328", "0.00000000000000000001e329"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const char* number : numbers)
|
|
||||||
{
|
|
||||||
for (const std::string text :
|
|
||||||
{
|
|
||||||
std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
INFO(text);
|
|
||||||
const bool parses = !json::parse(text, nullptr, false).is_discarded();
|
|
||||||
CHECK(json::accept(text) == parses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
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")
|
SECTION("BJData ND-array")
|
||||||
{
|
{
|
||||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||||
|
|||||||
@@ -237,274 +237,3 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
|
|||||||
CHECK(oj.dump() == "{\"a\":1}");
|
CHECK(oj.dump() == "{\"a\":1}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// the sizes around which the DOM parser changes how it stores the elements of
|
|
||||||
// an array (16) and the members of an object in a vector (32, and 64 for the
|
|
||||||
// key index)
|
|
||||||
const std::size_t container_sizes[] = {0, 1, 2, 3, 15, 16, 17, 18, 31, 32, 33, 34, 63, 64, 65, 66, 200};
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
std::string array_text(const std::size_t n, const std::size_t depth)
|
|
||||||
{
|
|
||||||
std::string text = "[";
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
// alternate scalars, empty containers and nested containers
|
|
||||||
switch (i % 4)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
text += std::to_string(i);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
text += "\"s" + std::to_string(i) + "\"";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text += depth == 0 ? "{}" : "{\"a\":" + array_text<Json>(n / 3, depth - 1) + ",\"b\":null}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
std::string object_text(const std::size_t n, const std::size_t depth, const bool duplicates)
|
|
||||||
{
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
text += "\"key" + std::to_string(i) + "\":";
|
|
||||||
switch (i % 4)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
text += std::to_string(i);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
text += depth == 0 ? "{}" : object_text<Json>(n / 2, depth - 1, duplicates);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text += "\"value\"";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (duplicates)
|
|
||||||
{
|
|
||||||
// keys that are there already, the first and the last one, with other values
|
|
||||||
text += n == 0 ? "" : ",";
|
|
||||||
text += "\"key0\":[1,2],\"key" + std::to_string(n - 1) + "\":{\"again\":true}";
|
|
||||||
}
|
|
||||||
return text + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void check_same_dump(const std::string& text)
|
|
||||||
{
|
|
||||||
// the text is what dump() writes for the parsed value, and the value
|
|
||||||
// survives a second round trip: arrays keep their elements in order, an
|
|
||||||
// ordered_json keeps its members in order of appearance
|
|
||||||
const Json parsed = Json::parse(text);
|
|
||||||
const Json again = Json::parse(parsed.dump());
|
|
||||||
CHECK(parsed == again);
|
|
||||||
CHECK(parsed.dump() == again.dump());
|
|
||||||
CHECK(Json::accept(text));
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parsing containers of sizes around the limits of the DOM parser")
|
|
||||||
{
|
|
||||||
SECTION("arrays")
|
|
||||||
{
|
|
||||||
for (const std::size_t n : container_sizes)
|
|
||||||
{
|
|
||||||
const std::string text = array_text<json>(n, 2);
|
|
||||||
INFO("size ", n);
|
|
||||||
|
|
||||||
const json parsed = json::parse(text);
|
|
||||||
CHECK(parsed.is_array());
|
|
||||||
CHECK(parsed.size() == n);
|
|
||||||
for (std::size_t i = 0; i < n; i += 4)
|
|
||||||
{
|
|
||||||
CHECK(parsed[i] == i);
|
|
||||||
}
|
|
||||||
check_same_dump<json>(text);
|
|
||||||
check_same_dump<ordered_json>(text);
|
|
||||||
CHECK(ordered_json::parse(text).dump() == parsed.dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects")
|
|
||||||
{
|
|
||||||
for (const bool duplicates :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const std::size_t n : container_sizes)
|
|
||||||
{
|
|
||||||
const std::string text = object_text<json>(n, 2, duplicates);
|
|
||||||
INFO("size ", n, " duplicates ", duplicates);
|
|
||||||
|
|
||||||
const json parsed = json::parse(text);
|
|
||||||
const ordered_json ordered = ordered_json::parse(text);
|
|
||||||
CHECK(parsed.is_object());
|
|
||||||
CHECK(ordered.is_object());
|
|
||||||
CHECK(parsed.size() == ordered.size());
|
|
||||||
// the same members, in the order of their first appearance
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
|
|
||||||
{
|
|
||||||
if (i < n)
|
|
||||||
{
|
|
||||||
CHECK(it.key() == "key" + std::to_string(i));
|
|
||||||
}
|
|
||||||
CHECK(parsed.at(it.key()) == json(it.value()));
|
|
||||||
}
|
|
||||||
if (duplicates && n > 1)
|
|
||||||
{
|
|
||||||
// the later value replaced the earlier one in its place
|
|
||||||
CHECK(ordered.at("key0") == ordered_json::parse("[1,2]"));
|
|
||||||
CHECK(ordered.at("key" + std::to_string(n - 1)) == ordered_json::parse("{\"again\":true}"));
|
|
||||||
CHECK(ordered.begin().key() == "key0");
|
|
||||||
CHECK(ordered.size() == n);
|
|
||||||
}
|
|
||||||
check_same_dump<json>(text);
|
|
||||||
check_same_dump<ordered_json>(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects with many different and repeated keys")
|
|
||||||
{
|
|
||||||
// 300 members of which a third repeat an earlier key
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < 300; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
text += "\"k" + std::to_string(i % 200) + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
text += "}";
|
|
||||||
|
|
||||||
const ordered_json ordered = ordered_json::parse(text);
|
|
||||||
const json parsed = json::parse(text);
|
|
||||||
CHECK(ordered.size() == 200);
|
|
||||||
CHECK(parsed.size() == 200);
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
|
|
||||||
{
|
|
||||||
CHECK(it.key() == "k" + std::to_string(i));
|
|
||||||
// the last value written
|
|
||||||
CHECK(it.value() == (i < 100 ? i + 200 : i));
|
|
||||||
CHECK(parsed.at(it.key()) == json(it.value()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a deterministic source of structure for parsing tests: builds a JSON text
|
|
||||||
// and, at the same time, the value it has to parse to
|
|
||||||
class tree_generator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit tree_generator(std::uint32_t seed) : state(seed) {}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
Json make(std::string& text, const std::size_t depth)
|
|
||||||
{
|
|
||||||
switch (next(depth == 0 ? 5 : 9))
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
text += "null";
|
|
||||||
return Json(nullptr);
|
|
||||||
case 1:
|
|
||||||
text += "true";
|
|
||||||
return Json(true);
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
const int n = static_cast<int>(next(2000)) - 1000;
|
|
||||||
text += std::to_string(n);
|
|
||||||
return Json(n);
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
text += "\"text\"";
|
|
||||||
return Json("text");
|
|
||||||
case 4:
|
|
||||||
text += "2.5";
|
|
||||||
return Json(2.5);
|
|
||||||
case 5:
|
|
||||||
case 6:
|
|
||||||
case 7:
|
|
||||||
{
|
|
||||||
Json array = Json::array();
|
|
||||||
text += "[";
|
|
||||||
const std::size_t n = size();
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
array.push_back(make<Json>(text, depth - 1));
|
|
||||||
}
|
|
||||||
text += "]";
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
Json object = Json::object();
|
|
||||||
text += "{";
|
|
||||||
const std::size_t n = size();
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
const std::string key = "k" + std::to_string(i);
|
|
||||||
text += (i == 0 ? "\"" : ",\"") + key + "\":";
|
|
||||||
object[key] = make<Json>(text, depth - 1);
|
|
||||||
}
|
|
||||||
text += "}";
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::uint32_t next(const std::uint32_t bound)
|
|
||||||
{
|
|
||||||
state = state * 1664525U + 1013904223U;
|
|
||||||
return (state >> 8) % bound;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size()
|
|
||||||
{
|
|
||||||
return container_sizes[next(sizeof(container_sizes) / sizeof(*container_sizes))];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t state;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parsing random trees of arrays and objects")
|
|
||||||
{
|
|
||||||
for (std::uint32_t seed = 1; seed <= 150; ++seed)
|
|
||||||
{
|
|
||||||
INFO("seed ", seed);
|
|
||||||
|
|
||||||
std::string text;
|
|
||||||
std::string ordered_text;
|
|
||||||
const json expected = tree_generator(seed).make<json>(text, 3);
|
|
||||||
const ordered_json ordered_expected = tree_generator(seed).make<ordered_json>(ordered_text, 3);
|
|
||||||
CHECK(text == ordered_text);
|
|
||||||
|
|
||||||
CHECK(json::parse(text) == expected);
|
|
||||||
CHECK(json::accept(text));
|
|
||||||
const ordered_json ordered = ordered_json::parse(text);
|
|
||||||
CHECK(ordered == ordered_expected);
|
|
||||||
CHECK(ordered.dump() == ordered_expected.dump());
|
|
||||||
CHECK(json::parse(text).dump() == expected.dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user