mirror of
https://github.com/nlohmann/json.git
synced 2026-10-25 15:28:54 +00:00
* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
52b72b5684
commit
fe5e401870
29 files changed
+3262
-3628
No files matched your search
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/output/error_handler.hpp>
|
||||
@@ -311,9 +312,7 @@ class binary_writer
|
||||
write_cbor_head(0x40, N);
|
||||
|
||||
// step 2: write each element
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
|
||||
N);
|
||||
write_bytes(j.m_data.m_value.binary->data(), N);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -326,7 +325,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "CBOR");
|
||||
throw_on_discarded(j, input_format_t::cbor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,9 +525,7 @@ class binary_writer
|
||||
}
|
||||
|
||||
// step 2: write the byte string
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
|
||||
N);
|
||||
write_bytes(j.m_data.m_value.binary->data(), N);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -542,7 +539,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
throw_on_discarded(j, input_format_t::msgpack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,7 +623,7 @@ class binary_writer
|
||||
case value_t::array:
|
||||
{
|
||||
bool prefix_required = true;
|
||||
const bool write_closer = write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
const bool write_closer = write_ubjson_start_container<true>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
|
||||
{
|
||||
const auto& array = *j.m_data.m_value.array;
|
||||
@@ -676,9 +673,7 @@ class binary_writer
|
||||
|
||||
if (use_type)
|
||||
{
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
|
||||
j.m_data.m_value.binary->size());
|
||||
write_bytes(j.m_data.m_value.binary->data(), j.m_data.m_value.binary->size());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -704,7 +699,7 @@ class binary_writer
|
||||
}
|
||||
|
||||
bool prefix_required = true;
|
||||
const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
const bool write_closer = write_ubjson_start_container<false>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
|
||||
write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth);
|
||||
|
||||
@@ -718,7 +713,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
throw_on_discarded(j, use_bjdata ? input_format_t::bjdata : input_format_t::ubjson);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,11 +739,11 @@ class binary_writer
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const input_format_t format)
|
||||
{
|
||||
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
static_cast<void>(format);
|
||||
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", input_format_name(format)), &j));
|
||||
}
|
||||
|
||||
/// how the range of integer type @a NumberType relates to that of @a TargetType
|
||||
@@ -868,11 +863,11 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format_name
|
||||
@brief throws because the integer @a j is too large for @a format
|
||||
@throw out_of_range.407 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
|
||||
static void throw_integer_out_of_range(const BasicJsonType& j, const input_format_t format, const char* range)
|
||||
{
|
||||
// neither std::to_string(), which has no overload for integer types
|
||||
// wider than 64 bits, nor dump(), which would also instantiate the
|
||||
@@ -883,9 +878,9 @@ class binary_writer
|
||||
? integer_to_string(j.m_data.m_value.number_unsigned, std::integral_constant<bool, unsigned_limits::is_signed>())
|
||||
: integer_to_string(j.m_data.m_value.number_integer, std::integral_constant<bool, integer_limits::is_signed>());
|
||||
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
static_cast<void>(format);
|
||||
static_cast<void>(range);
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", input_format_name(format), " as it does not fit ", range), &j));
|
||||
}
|
||||
|
||||
// The exceptions below are thrown from functions of their own, out of line:
|
||||
@@ -928,53 +923,56 @@ class binary_writer
|
||||
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
|
||||
}
|
||||
|
||||
/// @brief write @a size bytes starting at @a data to the output adapter
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_bytes(const void* data, const std::size_t size)
|
||||
{
|
||||
oa.write_characters(static_cast<const CharType*>(data), size);
|
||||
}
|
||||
|
||||
/// @brief write the MessagePack prefix of an array or object with @a N
|
||||
/// elements: the fix marker @a fix_marker | n, or @a marker_16 or
|
||||
/// @a marker_32 followed by the length
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_msgpack_container_prefix(const std::size_t N, const BasicJsonType& j,
|
||||
const std::uint8_t fix_marker, const std::uint8_t marker_16, const std::uint8_t marker_32)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
if (n <= 15)
|
||||
{
|
||||
// fixarray, fixmap
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(fix_marker | n)));
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
// array 16, map 16
|
||||
oa.write_character(to_char_type(marker_16));
|
||||
write_number(static_cast<std::uint16_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
// array 32, map 32
|
||||
oa.write_character(to_char_type(marker_32));
|
||||
write_number(static_cast<std::uint32_t>(n));
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
if (n <= 15)
|
||||
{
|
||||
// fixarray
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(0x90 | n)));
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
// array 16
|
||||
oa.write_character(to_char_type(0xDC));
|
||||
write_number(static_cast<std::uint16_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
// array 32
|
||||
oa.write_character(to_char_type(0xDD));
|
||||
write_number(static_cast<std::uint32_t>(n));
|
||||
}
|
||||
write_msgpack_container_prefix(N, j, 0x90, 0xDC, 0xDD);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_msgpack_object_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
if (n <= 15)
|
||||
{
|
||||
// fixmap
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(0x80 | (n & 0xF))));
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
// map 16
|
||||
oa.write_character(to_char_type(0xDE));
|
||||
write_number(static_cast<std::uint16_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
// map 32
|
||||
oa.write_character(to_char_type(0xDF));
|
||||
write_number(static_cast<std::uint32_t>(n));
|
||||
}
|
||||
write_msgpack_container_prefix(N, j, 0x80, 0xDE, 0xDF);
|
||||
}
|
||||
|
||||
/// @brief a CBOR, MessagePack, or BON8 array or object whose elements
|
||||
/// @ref write_cbor_iterative, @ref write_msgpack_iterative, or
|
||||
/// @ref write_bon8_iterative is still writing
|
||||
/// @brief a CBOR, MessagePack, BON8, or (as the base of @ref ubjson_frame)
|
||||
/// UBJSON array or object whose elements @ref write_container_iterative,
|
||||
/// @ref write_bon8_iterative, or @ref write_ubjson_iterative is still
|
||||
/// writing
|
||||
struct binary_container_frame
|
||||
{
|
||||
explicit binary_container_frame(const BasicJsonType* value_) noexcept
|
||||
@@ -1010,19 +1008,82 @@ class binary_writer
|
||||
typename BasicJsonType::array_t::const_iterator array_it{};
|
||||
};
|
||||
|
||||
/// @brief the CBOR specifics of @ref write_container_iterative
|
||||
struct cbor_walk
|
||||
{
|
||||
static void array_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
}
|
||||
|
||||
static void object_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||
}
|
||||
|
||||
static void scalar(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_cbor(j);
|
||||
}
|
||||
|
||||
static void string(binary_writer& w, const string_t& key, const BasicJsonType& context)
|
||||
{
|
||||
w.write_cbor_string(key, context);
|
||||
}
|
||||
|
||||
template<typename KeyType>
|
||||
static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object)
|
||||
{
|
||||
w.write_cbor_key(k, object);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief the MessagePack specifics of @ref write_container_iterative
|
||||
struct msgpack_walk
|
||||
{
|
||||
static void array_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
}
|
||||
|
||||
static void object_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
||||
}
|
||||
|
||||
static void scalar(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_msgpack(j);
|
||||
}
|
||||
|
||||
static void string(binary_writer& w, const string_t& key, const BasicJsonType& context)
|
||||
{
|
||||
w.write_msgpack_string(key, context);
|
||||
}
|
||||
|
||||
template<typename KeyType>
|
||||
static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object)
|
||||
{
|
||||
w.write_msgpack_key(k, object);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief write @a j with @ref write_cbor, or write its header and push a
|
||||
frame for @ref write_cbor_iterative to continue with its elements
|
||||
@brief write @a j with @a Walk::scalar, or write its header and push a
|
||||
frame for @ref write_container_iterative to continue with its
|
||||
elements
|
||||
|
||||
A scalar, and an empty array or object, are written out in full: there is
|
||||
nothing below them for @ref write_cbor_iterative to come back to, so
|
||||
nothing below them for @ref write_container_iterative to come back to, so
|
||||
nothing is pushed for them.
|
||||
*/
|
||||
void write_cbor_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
||||
template<typename Walk>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_container_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
||||
{
|
||||
if (j.is_array())
|
||||
{
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
Walk::array_head(*this, j);
|
||||
if (!j.m_data.m_value.array->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
@@ -1032,7 +1093,7 @@ class binary_writer
|
||||
|
||||
if (j.is_object())
|
||||
{
|
||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||
Walk::object_head(*this, j);
|
||||
if (!j.m_data.m_value.object->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
@@ -1040,22 +1101,26 @@ class binary_writer
|
||||
return;
|
||||
}
|
||||
|
||||
write_cbor(j);
|
||||
Walk::scalar(*this, j);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a root and everything below it without the call stack
|
||||
|
||||
Emits the same bytes as @ref write_cbor, keeping the containers it has
|
||||
Emits the same bytes as the recursive writer, keeping the containers it has
|
||||
entered on an explicit stack instead of descending into them. Only reached
|
||||
for values nested deeper than @ref recursion_depth_limit, which is why it
|
||||
is not written for speed.
|
||||
is not written for speed. Shared by CBOR and MessagePack, which differ only
|
||||
in the @a Walk policy (@ref cbor_walk, @ref msgpack_walk); BON8 has its own
|
||||
walker, as it tracks an open string run and closes a container with a
|
||||
marker.
|
||||
*/
|
||||
void write_cbor_iterative(const BasicJsonType& root)
|
||||
template<typename Walk>
|
||||
void write_container_iterative(const BasicJsonType& root)
|
||||
{
|
||||
// only a container with elements is ever pushed; see write_cbor_value_or_push
|
||||
// only a container with elements is ever pushed; see write_container_value_or_push
|
||||
std::vector<binary_container_frame> stack;
|
||||
write_cbor_value_or_push(root, stack);
|
||||
write_container_value_or_push<Walk>(root, stack);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -1073,7 +1138,7 @@ class binary_writer
|
||||
// read the child before pushing: entering it can move every frame
|
||||
const BasicJsonType* child = &(*current.array_it);
|
||||
++stack.back().array_it;
|
||||
write_cbor_value_or_push(*child, stack);
|
||||
write_container_value_or_push<Walk>(*child, stack);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1085,110 +1150,53 @@ class binary_writer
|
||||
}
|
||||
|
||||
// the key is written directly (not via a temporary basic_json),
|
||||
// with the object as diagnostics context, as in write_cbor
|
||||
write_cbor_key(current.object_it->first, *current.value);
|
||||
// with the object as diagnostics context, as in the recursive writer
|
||||
Walk::key(*this, current.object_it->first, *current.value);
|
||||
const BasicJsonType* child = &(current.object_it->second);
|
||||
++stack.back().object_it;
|
||||
write_cbor_value_or_push(*child, stack);
|
||||
write_container_value_or_push<Walk>(*child, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write @a j with @ref write_msgpack, or write its header and push a
|
||||
frame for @ref write_msgpack_iterative to continue with its elements
|
||||
|
||||
@sa @ref write_cbor_value_or_push
|
||||
*/
|
||||
void write_msgpack_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
||||
void write_cbor_iterative(const BasicJsonType& root)
|
||||
{
|
||||
if (j.is_array())
|
||||
{
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
if (!j.m_data.m_value.array->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.is_object())
|
||||
{
|
||||
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
||||
if (!j.m_data.m_value.object->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
write_msgpack(j);
|
||||
write_container_iterative<cbor_walk>(root);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a root and everything below it without the call stack
|
||||
|
||||
@sa @ref write_cbor_iterative
|
||||
*/
|
||||
void write_msgpack_iterative(const BasicJsonType& root)
|
||||
{
|
||||
std::vector<binary_container_frame> stack;
|
||||
write_msgpack_value_or_push(root, stack);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
const binary_container_frame current = stack.back();
|
||||
|
||||
if (current.value->is_array())
|
||||
{
|
||||
const auto& array = *current.value->m_data.m_value.array;
|
||||
if (current.array_it == array.cend())
|
||||
{
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const BasicJsonType* child = &(*current.array_it);
|
||||
++stack.back().array_it;
|
||||
write_msgpack_value_or_push(*child, stack);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& object = *current.value->m_data.m_value.object;
|
||||
if (current.object_it == object.cend())
|
||||
{
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
// as in write_cbor_iterative, the key is written directly with
|
||||
// the object as diagnostics context
|
||||
write_msgpack_key(current.object_it->first, *current.value);
|
||||
const BasicJsonType* child = &(current.object_it->second);
|
||||
++stack.back().object_it;
|
||||
write_msgpack_value_or_push(*child, stack);
|
||||
}
|
||||
}
|
||||
write_container_iterative<msgpack_walk>(root);
|
||||
}
|
||||
|
||||
/// @return true when a closing ']' still has to be written after the elements
|
||||
bool write_ubjson_start_array(const BasicJsonType& j, const bool use_count, const bool use_type,
|
||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
||||
/*!
|
||||
@brief write the start of an array (@a IsArray) or object: its opening
|
||||
marker, the optimized type, and the count
|
||||
|
||||
@return true when a closing ']' or '}' still has to be written after the
|
||||
elements
|
||||
*/
|
||||
template<bool IsArray>
|
||||
bool write_ubjson_start_container(const BasicJsonType& j, const bool use_count, const bool use_type,
|
||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
||||
{
|
||||
prefix_required = true;
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('['));
|
||||
oa.write_character(to_char_type(IsArray ? '[' : '{'));
|
||||
}
|
||||
|
||||
if (use_type && !j.m_data.m_value.array->empty())
|
||||
const std::size_t size = IsArray ? j.m_data.m_value.array->size() : j.m_data.m_value.object->size();
|
||||
|
||||
if (use_type && size != 0)
|
||||
{
|
||||
if (!use_count)
|
||||
{
|
||||
throw_size_marker_required(j);
|
||||
}
|
||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
|
||||
// the first element of an array has been compared with itself
|
||||
const bool same_prefix = std::all_of(IsArray ? j.begin() + 1 : j.begin(), j.end(),
|
||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||
{
|
||||
return ubjson_prefix(v, use_bjdata) == first_prefix;
|
||||
@@ -1200,8 +1208,9 @@ class binary_writer
|
||||
// those, at one byte per element, so the result can be read back.
|
||||
// Objects are not affected: every element is preceded by its key.
|
||||
const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
|
||||
const bool excessive_valueless = valueless_type
|
||||
&& j.m_data.m_value.array->size() > detail::max_valueless_container_size;
|
||||
const bool excessive_valueless = IsArray
|
||||
&& valueless_type
|
||||
&& size > detail::max_valueless_container_size;
|
||||
|
||||
if (same_prefix && !excessive_valueless
|
||||
&& !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
|
||||
@@ -1214,46 +1223,7 @@ class binary_writer
|
||||
|
||||
if (use_count)
|
||||
{
|
||||
write_ubjson_count(j.m_data.m_value.array->size(), use_bjdata);
|
||||
}
|
||||
|
||||
return !use_count;
|
||||
}
|
||||
|
||||
/// @return true when a closing '}' still has to be written after the elements
|
||||
bool write_ubjson_start_object(const BasicJsonType& j, const bool use_count, const bool use_type,
|
||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
||||
{
|
||||
prefix_required = true;
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('{'));
|
||||
}
|
||||
|
||||
if (use_type && !j.m_data.m_value.object->empty())
|
||||
{
|
||||
if (!use_count)
|
||||
{
|
||||
throw_size_marker_required(j);
|
||||
}
|
||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||
const bool same_prefix = std::all_of(j.begin(), j.end(),
|
||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||
{
|
||||
return ubjson_prefix(v, use_bjdata) == first_prefix;
|
||||
});
|
||||
|
||||
if (same_prefix && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
|
||||
{
|
||||
prefix_required = false;
|
||||
oa.write_character(to_char_type('$'));
|
||||
oa.write_character(first_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_count)
|
||||
{
|
||||
write_ubjson_count(j.m_data.m_value.object->size(), use_bjdata);
|
||||
write_ubjson_count(size, use_bjdata);
|
||||
}
|
||||
|
||||
return !use_count;
|
||||
@@ -1281,21 +1251,12 @@ class binary_writer
|
||||
|
||||
/// @brief an object or array @ref write_ubjson_iterative is still writing
|
||||
/// the elements of
|
||||
struct ubjson_frame
|
||||
struct ubjson_frame : binary_container_frame
|
||||
{
|
||||
ubjson_frame(const BasicJsonType* value_, const bool prefix_required_) noexcept
|
||||
: value(value_)
|
||||
: binary_container_frame(value_)
|
||||
, prefix_required(prefix_required_)
|
||||
{
|
||||
if (value->is_object())
|
||||
{
|
||||
object_it = value->m_data.m_value.object->cbegin();
|
||||
}
|
||||
else
|
||||
{
|
||||
array_it = value->m_data.m_value.array->cbegin();
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
// declared for GCC's -Weffc++, which asks for them in a class with
|
||||
// pointer members and a non-trivial destructor; the exception
|
||||
@@ -1307,13 +1268,9 @@ class binary_writer
|
||||
ubjson_frame& operator=(ubjson_frame&&) = default;
|
||||
~ubjson_frame() = default;
|
||||
|
||||
/// the array or object being written
|
||||
const BasicJsonType* value;
|
||||
/// whether value's elements each carry their own type marker; an
|
||||
/// optimized ($type) container writes it once for all of them instead
|
||||
bool prefix_required;
|
||||
typename BasicJsonType::object_t::const_iterator object_it{};
|
||||
typename BasicJsonType::array_t::const_iterator array_it{};
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -1324,7 +1281,7 @@ class binary_writer
|
||||
elements of an optimized container, and everything below the
|
||||
top level, never repeat it)
|
||||
|
||||
@sa @ref write_cbor_value_or_push
|
||||
@sa @ref write_container_value_or_push
|
||||
*/
|
||||
void write_ubjson_value_or_push(const BasicJsonType& j, const bool add_prefix, const bool use_count,
|
||||
const bool use_type, const bool use_bjdata, const bjdata_version_t bjdata_version,
|
||||
@@ -1347,11 +1304,11 @@ class binary_writer
|
||||
bool prefix_required = true;
|
||||
if (is_array)
|
||||
{
|
||||
write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
write_ubjson_start_container<true>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
write_ubjson_start_container<false>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
}
|
||||
|
||||
const bool empty = is_array ? j.m_data.m_value.array->empty() : j.m_data.m_value.object->empty();
|
||||
@@ -1361,7 +1318,7 @@ class binary_writer
|
||||
return;
|
||||
}
|
||||
|
||||
// write_ubjson_start_array/_object return !use_count, i.e. whether a
|
||||
// write_ubjson_start_container returns !use_count, i.e. whether a
|
||||
// closer still has to be written; use_count is constant for the whole
|
||||
// document, so that is recomputed here instead of being carried along
|
||||
if (!use_count)
|
||||
@@ -1550,14 +1507,15 @@ class binary_writer
|
||||
{
|
||||
oa.write_character(to_char_type(element_type));
|
||||
|
||||
if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name))
|
||||
if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep || error_handler == error_handler_t::strict))
|
||||
{
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(name.data()), name.size());
|
||||
write_bytes(name.data(), name.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
const string_t sanitized = sanitize_utf8(name, error_handler);
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(sanitized.data()), sanitized.size());
|
||||
string_t storage;
|
||||
const string_t& written = sanitize_utf8_for_bson_write(name, storage);
|
||||
write_bytes(written.data(), written.size());
|
||||
}
|
||||
|
||||
// the terminating null byte is written explicitly rather than taken
|
||||
@@ -1633,16 +1591,11 @@ class binary_writer
|
||||
{
|
||||
write_bson_entry_header(name, 0x02);
|
||||
|
||||
const bool sanitize = error_handler != error_handler_t::keep
|
||||
&& error_handler != error_handler_t::strict
|
||||
&& !is_valid_utf8(value);
|
||||
const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{};
|
||||
const string_t& written = sanitize ? sanitized : value;
|
||||
string_t storage;
|
||||
const string_t& written = sanitize_utf8_for_bson_write(value, storage);
|
||||
|
||||
write_number<std::int32_t>(to_bson_length(written.size() + 1ul), true);
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(written.data()),
|
||||
written.size());
|
||||
write_bytes(written.data(), written.size());
|
||||
// the terminating null byte is written explicitly rather than taken
|
||||
// from the buffer, so that string_t::data() need not be null-terminated
|
||||
oa.write_character(to_char_type(0x00));
|
||||
@@ -1748,7 +1701,7 @@ class binary_writer
|
||||
|
||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
write_bytes(value.data(), value.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1778,14 +1731,14 @@ class binary_writer
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "int64");
|
||||
throw_integer_out_of_range(j, input_format_t::bson, "int64");
|
||||
}
|
||||
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "uint64");
|
||||
throw_integer_out_of_range(j, input_format_t::bson, "uint64");
|
||||
}
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
@@ -1796,7 +1749,7 @@ class binary_writer
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
throw_on_discarded(j, input_format_t::bson);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
@@ -1960,7 +1913,7 @@ class binary_writer
|
||||
// the terminating null byte is written explicitly rather than
|
||||
// taken from the buffer, so that string_t::data() need not be
|
||||
// null-terminated
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
write_bytes(value.data(), value.size());
|
||||
oa.write_character(to_char_type(0x00));
|
||||
return true;
|
||||
}
|
||||
@@ -1977,7 +1930,7 @@ class binary_writer
|
||||
p += sizeof(std::int32_t);
|
||||
*p++ = value.has_subtype() ? to_char_type(static_cast<std::uint8_t>(value.subtype())) : to_char_type(0x00);
|
||||
oa.commit(static_cast<std::size_t>(p - out));
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
write_bytes(value.data(), value.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2025,7 +1978,7 @@ class binary_writer
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
throw_on_discarded(j, input_format_t::bson);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
@@ -2806,7 +2759,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "uint64");
|
||||
throw_integer_out_of_range(j, input_format_t::cbor, "uint64");
|
||||
}
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
@@ -2816,7 +2769,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::cbor, "[-2^64, 2^64-1]");
|
||||
}
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
@@ -2827,7 +2780,7 @@ class binary_writer
|
||||
// a negative integer n is encoded as -1 - n
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::cbor, "[-2^64, 2^64-1]");
|
||||
}
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
@@ -2903,7 +2856,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "uint64");
|
||||
throw_integer_out_of_range(j, input_format_t::msgpack, "uint64");
|
||||
}
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
@@ -2913,7 +2866,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::msgpack, "[-2^63, 2^64-1]");
|
||||
}
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
@@ -2923,7 +2876,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::msgpack, "[-2^63, 2^64-1]");
|
||||
}
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
@@ -3057,40 +3010,37 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an object key as a CBOR text string
|
||||
@brief write an object key as a CBOR or MessagePack string (@a Walk)
|
||||
|
||||
A key convertible to string_t is written directly. Other key types (only
|
||||
an explicit conversion, or only a to_json overload) go through a temporary
|
||||
basic_json, as in version 3.12.0; the temporary is then the diagnostics
|
||||
context for strict UTF-8 checks.
|
||||
*/
|
||||
template<typename Key = typename BasicJsonType::object_t::key_type,
|
||||
template<typename Walk, typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
|
||||
void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
|
||||
{
|
||||
Walk::string(*this, key, context);
|
||||
}
|
||||
|
||||
template<typename Walk, typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t<negation<std::is_convertible<Key, string_t>>::value, int> = 0>
|
||||
void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
|
||||
{
|
||||
Walk::scalar(*this, BasicJsonType(key));
|
||||
}
|
||||
|
||||
/// @brief write an object key as a CBOR text string
|
||||
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
|
||||
{
|
||||
write_cbor_string(key, context);
|
||||
write_object_key<cbor_walk>(key, context);
|
||||
}
|
||||
|
||||
template < typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
|
||||
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
|
||||
{
|
||||
write_cbor(BasicJsonType(key));
|
||||
}
|
||||
|
||||
/// @brief write an object key as a MessagePack str, as in @ref write_cbor_key
|
||||
template<typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
|
||||
/// @brief write an object key as a MessagePack str
|
||||
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
|
||||
{
|
||||
write_msgpack_string(key, context);
|
||||
}
|
||||
|
||||
template < typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
|
||||
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
|
||||
{
|
||||
write_msgpack(BasicJsonType(key));
|
||||
write_object_key<msgpack_walk>(key, context);
|
||||
}
|
||||
|
||||
/// the longest string that is written, with its head of one or two
|
||||
@@ -3165,9 +3115,7 @@ class binary_writer
|
||||
write_cbor_head(0x60, sanitized.size());
|
||||
|
||||
// step 2: write the string
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||
sanitized.size());
|
||||
write_bytes(sanitized.data(), sanitized.size());
|
||||
}
|
||||
|
||||
/////////////
|
||||
@@ -3248,9 +3196,7 @@ class binary_writer
|
||||
}
|
||||
|
||||
// step 2: write the string
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||
sanitized.size());
|
||||
write_bytes(sanitized.data(), sanitized.size());
|
||||
}
|
||||
|
||||
////////////
|
||||
@@ -3533,9 +3479,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type('S'));
|
||||
}
|
||||
write_number_with_ubjson_prefix(sanitized.size(), true, use_bjdata);
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||
sanitized.size());
|
||||
write_bytes(sanitized.data(), sanitized.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4335,7 +4279,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
throw_integer_out_of_range(j, input_format_t::bon8, "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
@@ -4346,7 +4290,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
throw_integer_out_of_range(j, input_format_t::bon8, "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
@@ -4414,7 +4358,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "BON8");
|
||||
throw_on_discarded(j, input_format_t::bon8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4425,7 +4369,7 @@ class binary_writer
|
||||
A scalar, and an empty array or object, are written out in full and not
|
||||
pushed.
|
||||
|
||||
@sa @ref write_cbor_value_or_push
|
||||
@sa @ref write_container_value_or_push
|
||||
*/
|
||||
void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector<binary_container_frame>& stack)
|
||||
{
|
||||
@@ -4811,7 +4755,7 @@ class binary_writer
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(s.data()), s.size());
|
||||
write_bytes(s.data(), s.size());
|
||||
string_open = true;
|
||||
}
|
||||
}
|
||||
@@ -4902,6 +4846,31 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the write-pass counterpart of @ref sanitize_utf8_for_write for BSON
|
||||
|
||||
The size pass (@ref calc_bson_entry_header_size, @ref
|
||||
calc_bson_string_size) has already validated @a s, and thrown for @ref
|
||||
error_handler_t::strict if it was ill-formed. Here, @ref
|
||||
error_handler_t::strict therefore behaves like @ref error_handler_t::keep,
|
||||
and @ref error_handler_t::replace / @ref error_handler_t::ignore produce
|
||||
exactly the bytes the size pass measured. Hence no context is needed.
|
||||
|
||||
@param[in] s the string (value or object key) to write
|
||||
@param[out] storage backing storage for a sanitized copy
|
||||
|
||||
@return a reference to @a s, or to @a storage once it holds a sanitized copy
|
||||
*/
|
||||
const string_t& sanitize_utf8_for_bson_write(const string_t& s, string_t& storage) const
|
||||
{
|
||||
if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(s))
|
||||
{
|
||||
return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call
|
||||
}
|
||||
storage = sanitize_utf8(s, error_handler);
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used
|
||||
template < typename T, enable_if_t < !std::is_same<T, string_t>::value, int > = 0 >
|
||||
const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface
|
||||
|
||||
@@ -966,6 +966,28 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// write out the bytes pending in @ref string_buffer and reset @a bytes
|
||||
JSON_HEDLEY_ALWAYS_INLINE void emit_pending(std::size_t& bytes)
|
||||
{
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// write buffer and reset index if fewer than 13 bytes are left, as this
|
||||
/// is the maximal number of bytes to be written ("\uxxxx\uxxxx\0") for one
|
||||
/// code point
|
||||
JSON_HEDLEY_ALWAYS_INLINE void reserve_string_buffer(std::size_t& bytes)
|
||||
{
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief worker for @ref dump_escaped
|
||||
|
||||
@@ -1024,11 +1046,7 @@ class serializer
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order, then write the run directly
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
emit_pending(bytes);
|
||||
put_run(s, i, run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
@@ -1049,11 +1067,10 @@ class serializer
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
emit_pending(bytes);
|
||||
// deliberately not put_buffer(): a fixed 8-byte memcpy
|
||||
// has no asserts and no write-through branch, which
|
||||
// matters on the hot path
|
||||
const auto& escape = ascii_escapes()[data[i]];
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
||||
{
|
||||
@@ -1128,14 +1145,7 @@ class serializer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
// remember the byte position of this accept
|
||||
bytes_after_last_accept = bytes;
|
||||
@@ -1187,14 +1197,7 @@ class serializer
|
||||
string_buffer[bytes++] = '\xBD';
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
}
|
||||
@@ -1227,14 +1230,7 @@ class serializer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
undumped_chars = 0;
|
||||
@@ -1600,30 +1596,17 @@ class serializer
|
||||
*/
|
||||
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 256> copyable =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
|
||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0
|
||||
}
|
||||
};
|
||||
// constant-initialized from the predicate: no runtime loop, no guard
|
||||
static constexpr std::array<std::uint8_t, 256> copyable = make_ascii_copyable(make_index_sequence<256> {});
|
||||
return copyable;
|
||||
}
|
||||
|
||||
template<std::size_t... Is>
|
||||
static constexpr std::array<std::uint8_t, 256> make_ascii_copyable(index_sequence<Is...> /*unused*/) noexcept
|
||||
{
|
||||
return {{ static_cast<std::uint8_t>(is_ascii_copyable(static_cast<unsigned char>(Is)) ? 1 : 0)... }};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the JSON form of each character below 0x60
|
||||
|
||||
@@ -1752,7 +1735,6 @@ class serializer
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||
{
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
std::size_t i = 0;
|
||||
while (i < n && data[i] >= 0x80)
|
||||
{
|
||||
@@ -1789,26 +1771,21 @@ class serializer
|
||||
char* d = write_buffer.data() + write_buffer_pos;
|
||||
if (codepoint > 0xFFFFu)
|
||||
{
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||
}
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint));
|
||||
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||
i += length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit) noexcept
|
||||
{
|
||||
const auto unit = static_cast<std::uint32_t>(codeunit);
|
||||
d[0] = '\\';
|
||||
d[1] = 'u';
|
||||
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||
d[5] = nibble_to_hex[unit & 0x0Fu];
|
||||
return d + 6;
|
||||
return hex4_lower(codeunit, d + 2);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1817,7 +1794,7 @@ class serializer
|
||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||
* lookup table (@ref hex4_lower), avoiding the format-string parsing and locale machinery of
|
||||
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||
*
|
||||
* @param[in] pos position in @a string_buffer to write at; there must
|
||||
@@ -1827,13 +1804,9 @@ class serializer
|
||||
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||
{
|
||||
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
string_buffer[pos + 0] = '\\';
|
||||
string_buffer[pos + 1] = 'u';
|
||||
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||
hex4_lower(codeunit, &string_buffer[pos + 2]);
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user