diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 71566ace5..312d2e388 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -305,10 +305,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_literal(", "); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\n"); @@ -336,10 +336,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_char(','); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\"subtype\":"); @@ -656,10 +656,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_literal(", "); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\n"); @@ -687,10 +687,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_char(','); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\"subtype\":"); @@ -780,6 +780,20 @@ class serializer characters by a sequence of "\u" followed by a four-digit hex representation. The escaped string is written to output stream @a o. + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + @param[in] s the string to escape @param[in] ensure_ascii whether to escape non-ASCII characters with \uXXXX sequences @@ -787,6 +801,22 @@ class serializer @complexity Linear in the length of string @a s. */ void dump_escaped(const string_t& s, const bool ensure_ascii) + { + // dispatch once here rather than test the flag inside the loop: it does + // not change while a string is written, and folding it lets each of the + // two scanners be inlined into a loop of its own + if (ensure_ascii) + { + dump_escaped_impl(s); + } + else + { + dump_escaped_impl(s); + } + } + + template + void dump_escaped_impl(const string_t& s) { std::uint32_t codepoint{}; std::uint8_t state = UTF8_ACCEPT; @@ -805,16 +835,16 @@ class serializer // individually, so that byte is left to the byte-at-a-time path // below, keeping escaping output and error diagnostics unchanged. // - // - ensure_ascii == false: string_bulk_run() copies ordinary bytes + // - EnsureAscii == false: string_bulk_run() copies ordinary bytes // and complete well-formed UTF-8, stopping at a quote, backslash, // control character (< 0x20), or ill-formed/truncated sequence. - // - ensure_ascii == true: only printable ASCII may be copied + // - EnsureAscii == true: only printable ASCII may be copied // verbatim; find_ascii_copyable_run() additionally stops at 0x7F // and every non-ASCII byte (>= 0x80), which must be \u-escaped. if (state == UTF8_ACCEPT) { const auto* const data = reinterpret_cast(s.data()); - const std::size_t run = ensure_ascii + const std::size_t run = EnsureAscii ? find_ascii_copyable_run(data + i, s.size() - i) : string_bulk_run(data + i, s.size() - i); if (run != 0) @@ -897,8 +927,8 @@ class serializer default: { // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + // EnsureAscii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F))) { if (codepoint <= 0xFFFF) { @@ -963,7 +993,7 @@ class serializer if (error_handler == error_handler_t::replace) { // add a replacement character - if (ensure_ascii) + if (EnsureAscii) { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'u'; @@ -1006,7 +1036,7 @@ class serializer default: // decode found yet incomplete multibyte code point { - if (!ensure_ascii) + if (!EnsureAscii) { // code point will not be escaped - copy byte to buffer string_buffer[bytes++] = s[i]; @@ -1048,7 +1078,7 @@ class serializer // write all accepted bytes put_buffer(string_buffer, bytes_after_last_accept); // add a replacement character - if (ensure_ascii) + if (EnsureAscii) { put_literal("\\ufffd"); } @@ -1320,6 +1350,57 @@ class serializer return false; } + /*! + @brief write the decimal representation of the byte @a value + + A binary value's bytes are always in [0, 255], so writing one needs neither + the digit counting nor the 64-bit arithmetic that @ref dump_integer does for + an arbitrary number, and the three digits it takes at most are written + straight into the write buffer. + + Any byte type that is not a plain unsigned byte is left to @ref dump_integer, + whose representation of it may differ. + */ + template + void dump_byte(const ByteType value) + { + dump_byte(value, std::integral_constant < bool, + std::is_unsigned::value && sizeof(ByteType) == 1 + && !std::is_same::value > {}); + } + + template + void dump_byte(const ByteType value, std::false_type /*is_plain_byte*/) + { + dump_integer(value); + } + + template + void dump_byte(const ByteType value, std::true_type /*is_plain_byte*/) + { + if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + 3 > write_buffer.size())) + { + flush(); + } + + const auto byte = static_cast(value); + char* out = write_buffer.data() + write_buffer_pos; + + if (byte >= 100) + { + *out++ = static_cast('0' + (byte / 100)); + *out++ = static_cast('0' + ((byte / 10) % 10)); + } + else if (byte >= 10) + { + *out++ = static_cast('0' + (byte / 10)); + } + + *out++ = static_cast('0' + (byte % 10)); + + write_buffer_pos = static_cast(out - write_buffer.data()); + } + /*! @brief dump an integer diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8901a619c..b304baab3 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -21238,10 +21238,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_literal(", "); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\n"); @@ -21269,10 +21269,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_char(','); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\"subtype\":"); @@ -21589,10 +21589,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_literal(", "); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\n"); @@ -21620,10 +21620,10 @@ class serializer for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { - dump_integer(*i); + dump_byte(*i); put_char(','); } - dump_integer(val.m_data.m_value.binary->back()); + dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\"subtype\":"); @@ -21713,6 +21713,20 @@ class serializer characters by a sequence of "\u" followed by a four-digit hex representation. The escaped string is written to output stream @a o. + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + @param[in] s the string to escape @param[in] ensure_ascii whether to escape non-ASCII characters with \uXXXX sequences @@ -21720,6 +21734,22 @@ class serializer @complexity Linear in the length of string @a s. */ void dump_escaped(const string_t& s, const bool ensure_ascii) + { + // dispatch once here rather than test the flag inside the loop: it does + // not change while a string is written, and folding it lets each of the + // two scanners be inlined into a loop of its own + if (ensure_ascii) + { + dump_escaped_impl(s); + } + else + { + dump_escaped_impl(s); + } + } + + template + void dump_escaped_impl(const string_t& s) { std::uint32_t codepoint{}; std::uint8_t state = UTF8_ACCEPT; @@ -21738,16 +21768,16 @@ class serializer // individually, so that byte is left to the byte-at-a-time path // below, keeping escaping output and error diagnostics unchanged. // - // - ensure_ascii == false: string_bulk_run() copies ordinary bytes + // - EnsureAscii == false: string_bulk_run() copies ordinary bytes // and complete well-formed UTF-8, stopping at a quote, backslash, // control character (< 0x20), or ill-formed/truncated sequence. - // - ensure_ascii == true: only printable ASCII may be copied + // - EnsureAscii == true: only printable ASCII may be copied // verbatim; find_ascii_copyable_run() additionally stops at 0x7F // and every non-ASCII byte (>= 0x80), which must be \u-escaped. if (state == UTF8_ACCEPT) { const auto* const data = reinterpret_cast(s.data()); - const std::size_t run = ensure_ascii + const std::size_t run = EnsureAscii ? find_ascii_copyable_run(data + i, s.size() - i) : string_bulk_run(data + i, s.size() - i); if (run != 0) @@ -21830,8 +21860,8 @@ class serializer default: { // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + // EnsureAscii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F))) { if (codepoint <= 0xFFFF) { @@ -21896,7 +21926,7 @@ class serializer if (error_handler == error_handler_t::replace) { // add a replacement character - if (ensure_ascii) + if (EnsureAscii) { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'u'; @@ -21939,7 +21969,7 @@ class serializer default: // decode found yet incomplete multibyte code point { - if (!ensure_ascii) + if (!EnsureAscii) { // code point will not be escaped - copy byte to buffer string_buffer[bytes++] = s[i]; @@ -21981,7 +22011,7 @@ class serializer // write all accepted bytes put_buffer(string_buffer, bytes_after_last_accept); // add a replacement character - if (ensure_ascii) + if (EnsureAscii) { put_literal("\\ufffd"); } @@ -22253,6 +22283,57 @@ class serializer return false; } + /*! + @brief write the decimal representation of the byte @a value + + A binary value's bytes are always in [0, 255], so writing one needs neither + the digit counting nor the 64-bit arithmetic that @ref dump_integer does for + an arbitrary number, and the three digits it takes at most are written + straight into the write buffer. + + Any byte type that is not a plain unsigned byte is left to @ref dump_integer, + whose representation of it may differ. + */ + template + void dump_byte(const ByteType value) + { + dump_byte(value, std::integral_constant < bool, + std::is_unsigned::value && sizeof(ByteType) == 1 + && !std::is_same::value > {}); + } + + template + void dump_byte(const ByteType value, std::false_type /*is_plain_byte*/) + { + dump_integer(value); + } + + template + void dump_byte(const ByteType value, std::true_type /*is_plain_byte*/) + { + if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + 3 > write_buffer.size())) + { + flush(); + } + + const auto byte = static_cast(value); + char* out = write_buffer.data() + write_buffer_pos; + + if (byte >= 100) + { + *out++ = static_cast('0' + (byte / 100)); + *out++ = static_cast('0' + ((byte / 10) % 10)); + } + else if (byte >= 10) + { + *out++ = static_cast('0' + (byte / 10)); + } + + *out++ = static_cast('0' + (byte % 10)); + + write_buffer_pos = static_cast(out - write_buffer.data()); + } + /*! @brief dump an integer