// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #pragma once #include // reverse, remove, fill, find, none_of, min #include // array #include // localeconv, lconv #include // labs, isfinite, isnan, signbit #include // size_t, ptrdiff_t #include // uint8_t #include // snprintf #include // memcpy, memset #include // numeric_limits #include // string, char_traits #include // setfill, setw #include // is_same #include // move #include // vector #include #include #include #include #include #include #include #include #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { /////////////////// // serialization // /////////////////// /// how to treat decoding errors enum class error_handler_t { strict, ///< throw a type_error exception in case of invalid UTF-8 replace, ///< replace invalid UTF-8 sequences with U+FFFD ignore ///< ignore invalid UTF-8 sequences }; template class serializer { using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using binary_char_t = typename BasicJsonType::binary_t::value_type; static constexpr std::uint8_t UTF8_ACCEPT = 0; static constexpr std::uint8_t UTF8_REJECT = 1; public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use @param[in] error_handler_ how to react on decoding errors */ serializer(output_adapter_t s, const char ichar, error_handler_t error_handler_ = error_handler_t::strict) : o(std::move(s)) , loc(std::localeconv()) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) , indent_char(ichar) , error_handler(error_handler_) {} // deleted because of pointer members serializer(const serializer&) = delete; serializer& operator=(const serializer&) = delete; serializer(serializer&&) = delete; serializer& operator=(serializer&&) = delete; ~serializer() = default; /*! @brief internal implementation of the serialization function This function is called by the public member function dump and organizes the serialization internally. The indentation level is propagated as additional parameter. Arrays and objects are serialized without recursion, however deeply they are nested. - strings and object keys are escaped using `escape_string()` - integer numbers are converted implicitly via `operator<<` - floating-point numbers are converted to a string using `"%g"` format - binary values are serialized as objects containing the subtype and the byte array @param[in] val value to serialize @param[in] pretty_print whether the output shall be pretty-printed @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ void dump(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const std::size_t indent_step, const std::size_t current_indent = 0) { dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent); flush(); } JSON_PRIVATE_UNLESS_TESTED: /*! @brief worker for @ref dump Identical in behavior to the historical @ref dump, but writes into the serializer's internal @ref write_buffer instead of issuing a virtual call per token. The public @ref dump wraps this and flushes the buffer once the top-level value has been serialized. Serializing a container descends into its elements, so a value nested deeply enough used to exhaust the call stack and terminate the process with no exception to catch. The descent is bounded here: once @ref dump_depth_limit levels have been entered, @ref dump_iteratively writes out what is left without the call stack. A value nested less deeply than that - all but a vanishing minority - is written by exactly the code that always wrote it. @sa https://github.com/nlohmann/json/issues/5387 */ void dump_internal(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const std::size_t indent_step, const std::size_t current_indent = 0, const std::size_t depth = 0) { switch (val.m_data.m_type) { case value_t::object: { if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit())) { dump_iteratively(val, pretty_print, ensure_ascii, indent_step, current_indent); return; } if (val.m_data.m_value.object->empty()) { put_literal("{}"); return; } if (pretty_print) { put_literal("{\n"); // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); // first n-1 elements auto i = val.m_data.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) { put_indent(new_indent); put_char('\"'); dump_escaped(i->first, ensure_ascii); put_literal("\": "); dump_internal(i->second, true, ensure_ascii, indent_step, new_indent, depth + 1); put_literal(",\n"); } // last element JSON_ASSERT(i != val.m_data.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); put_indent(new_indent); put_char('\"'); dump_escaped(i->first, ensure_ascii); put_literal("\": "); dump_internal(i->second, true, ensure_ascii, indent_step, new_indent, depth + 1); put_char('\n'); put_indent(current_indent); put_char('}'); } else { put_char('{'); // first n-1 elements auto i = val.m_data.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) { put_char('\"'); dump_escaped(i->first, ensure_ascii); put_literal("\":"); dump_internal(i->second, false, ensure_ascii, indent_step, current_indent, depth + 1); put_char(','); } // last element JSON_ASSERT(i != val.m_data.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); put_char('\"'); dump_escaped(i->first, ensure_ascii); put_literal("\":"); dump_internal(i->second, false, ensure_ascii, indent_step, current_indent, depth + 1); put_char('}'); } return; } case value_t::array: { if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit())) { dump_iteratively(val, pretty_print, ensure_ascii, indent_step, current_indent); return; } if (val.m_data.m_value.array->empty()) { put_literal("[]"); return; } if (pretty_print) { put_literal("[\n"); // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); // first n-1 elements for (auto i = val.m_data.m_value.array->cbegin(); i != val.m_data.m_value.array->cend() - 1; ++i) { put_indent(new_indent); dump_internal(*i, true, ensure_ascii, indent_step, new_indent, depth + 1); put_literal(",\n"); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); put_indent(new_indent); dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent, depth + 1); put_char('\n'); put_indent(current_indent); put_char(']'); } else { put_char('['); // first n-1 elements for (auto i = val.m_data.m_value.array->cbegin(); i != val.m_data.m_value.array->cend() - 1; ++i) { dump_internal(*i, false, ensure_ascii, indent_step, current_indent, depth + 1); put_char(','); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); dump_internal(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent, depth + 1); put_char(']'); } return; } case value_t::string: { put_char('\"'); dump_escaped(*val.m_data.m_value.string, ensure_ascii); put_char('\"'); return; } case value_t::binary: { if (pretty_print) { put_literal("{\n"); // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); put_indent(new_indent); put_literal("\"bytes\": ["); if (!val.m_data.m_value.binary->empty()) { for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { dump_byte(*i); put_literal(", "); } dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\n"); put_indent(new_indent); put_literal("\"subtype\": "); if (val.m_data.m_value.binary->has_subtype()) { dump_integer(val.m_data.m_value.binary->subtype()); } else { put_literal("null"); } put_char('\n'); put_indent(current_indent); put_char('}'); } else { put_literal("{\"bytes\":["); if (!val.m_data.m_value.binary->empty()) { for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { dump_byte(*i); put_char(','); } dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\"subtype\":"); if (val.m_data.m_value.binary->has_subtype()) { dump_integer(val.m_data.m_value.binary->subtype()); put_char('}'); } else { put_literal("null}"); } } return; } case value_t::boolean: { if (val.m_data.m_value.boolean) { put_literal("true"); } else { put_literal("false"); } return; } case value_t::number_integer: { dump_integer(val.m_data.m_value.number_integer); return; } case value_t::number_unsigned: { dump_integer(val.m_data.m_value.number_unsigned); return; } case value_t::number_float: { dump_float(val.m_data.m_value.number_float); return; } case value_t::discarded: { put_literal(""); return; } case value_t::null: { put_literal("null"); return; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } } private: /// the number of levels @ref dump_internal descends into before it hands /// over to @ref dump_iteratively static constexpr std::size_t dump_depth_limit() { return 128; } /*! @brief write out @a val and everything below it without the call stack Emits the same bytes as @ref dump_internal, keeping the containers it has entered on an explicit stack instead of descending into them. Only reached for values nested deeper than @ref dump_depth_limit, which is why it is not written for speed: walking every value this way measured up to 20% slower on object-heavy documents than letting the compiler drive the descent. */ void dump_iteratively(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const std::size_t indent_step, const std::size_t current_indent = 0) { // Scalars, empty containers and binary values are written by dump_value // alone, so nothing is allocated for them: only a container with // elements is ever pushed. std::vector stack; dump_value(val, pretty_print, ensure_ascii, indent_step, current_indent, stack); while (!stack.empty()) { dump_frame& frame = stack.back(); if (frame.value->m_data.m_type == value_t::object) { const auto* object = frame.value->m_data.m_value.object; if (frame.object_it == object->cend()) { if (pretty_print) { put_char('\n'); put_indent(frame.current_indent); } put_char('}'); stack.pop_back(); continue; } // the separator goes in front of every element but the first, // which puts exactly one between each pair and none at the end if (frame.object_it != object->cbegin()) { if (pretty_print) { put_literal(",\n"); } else { put_char(','); } } if (pretty_print) { put_indent(frame.child_indent); } put_char('\"'); dump_escaped(frame.object_it->first, ensure_ascii); if (pretty_print) { put_literal("\": "); } else { put_literal("\":"); } const BasicJsonType& element = frame.object_it->second; ++frame.object_it; // read everything needed from the frame before this: entering a // container pushes another one and can move them all const std::size_t element_indent = frame.child_indent; dump_value(element, pretty_print, ensure_ascii, indent_step, element_indent, stack); } else { const auto* array = frame.value->m_data.m_value.array; if (frame.array_it == array->cend()) { if (pretty_print) { put_char('\n'); put_indent(frame.current_indent); } put_char(']'); stack.pop_back(); continue; } if (frame.array_it != array->cbegin()) { if (pretty_print) { put_literal(",\n"); } else { put_char(','); } } if (pretty_print) { put_indent(frame.child_indent); } const BasicJsonType& element = *frame.array_it; ++frame.array_it; // see above const std::size_t element_indent = frame.child_indent; dump_value(element, pretty_print, ensure_ascii, indent_step, element_indent, stack); } } } private: /// @brief a container that has been opened but not closed yet struct dump_frame { dump_frame(const BasicJsonType* value_, const std::size_t current_indent_, const std::size_t child_indent_) noexcept : value(value_) , current_indent(current_indent_) , child_indent(child_indent_) {} /// the object or array being serialized const BasicJsonType* value; /// the element to serialize next; which of the two is live follows from /// the type of @a value. They are kept side by side rather than in a /// union, which would need its special members written out by hand, see /// detail/iterators/internal_iterator.hpp typename BasicJsonType::object_t::const_iterator object_it{}; typename BasicJsonType::array_t::const_iterator array_it{}; /// the indentation of the container itself, used by its closing bracket std::size_t current_indent; /// the indentation of the container's elements std::size_t child_indent; }; /*! @brief serialize the value @a val, but not the elements of a container An object or array with elements is opened and pushed onto @a stack for @ref dump_internal to walk; everything else - including a binary value, which looks like an object but has no elements to descend into - is written out here in full. */ void dump_value(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const std::size_t indent_step, const std::size_t current_indent, std::vector& stack) { switch (val.m_data.m_type) { case value_t::object: { if (val.m_data.m_value.object->empty()) { put_literal("{}"); return; } std::size_t child_indent = current_indent; if (pretty_print) { put_literal("{\n"); child_indent = next_indent(current_indent, indent_step); } else { put_char('{'); } stack.emplace_back(&val, current_indent, child_indent); stack.back().object_it = val.m_data.m_value.object->cbegin(); return; } case value_t::array: { if (val.m_data.m_value.array->empty()) { put_literal("[]"); return; } std::size_t child_indent = current_indent; if (pretty_print) { put_literal("[\n"); child_indent = next_indent(current_indent, indent_step); } else { put_char('['); } stack.emplace_back(&val, current_indent, child_indent); stack.back().array_it = val.m_data.m_value.array->cbegin(); return; } case value_t::string: { put_char('\"'); dump_escaped(*val.m_data.m_value.string, ensure_ascii); put_char('\"'); return; } case value_t::binary: { if (pretty_print) { put_literal("{\n"); // variable to hold indentation for the bytes const auto new_indent = next_indent(current_indent, indent_step); put_indent(new_indent); put_literal("\"bytes\": ["); if (!val.m_data.m_value.binary->empty()) { for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { dump_byte(*i); put_literal(", "); } dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\n"); put_indent(new_indent); put_literal("\"subtype\": "); if (val.m_data.m_value.binary->has_subtype()) { dump_integer(val.m_data.m_value.binary->subtype()); } else { put_literal("null"); } put_char('\n'); put_indent(current_indent); put_char('}'); } else { put_literal("{\"bytes\":["); if (!val.m_data.m_value.binary->empty()) { for (auto i = val.m_data.m_value.binary->cbegin(); i != val.m_data.m_value.binary->cend() - 1; ++i) { dump_byte(*i); put_char(','); } dump_byte(val.m_data.m_value.binary->back()); } put_literal("],\"subtype\":"); if (val.m_data.m_value.binary->has_subtype()) { dump_integer(val.m_data.m_value.binary->subtype()); put_char('}'); } else { put_literal("null}"); } } return; } case value_t::boolean: { if (val.m_data.m_value.boolean) { put_literal("true"); } else { put_literal("false"); } return; } case value_t::number_integer: { dump_integer(val.m_data.m_value.number_integer); return; } case value_t::number_unsigned: { dump_integer(val.m_data.m_value.number_unsigned); return; } case value_t::number_float: { dump_float(val.m_data.m_value.number_float); return; } case value_t::discarded: { put_literal(""); return; } case value_t::null: { put_literal("null"); return; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } } /*! @brief the indentation level to use for the children of the current value A very large @a indent_step can wrap the unsigned accumulation on deep nesting, which would silently truncate the indentation. Far harder to reach now that the accumulator is a std::size_t, but still reachable where that is 32 bits wide. */ static std::size_t next_indent(const std::size_t current_indent, const std::size_t indent_step) { const std::size_t new_indent = current_indent + indent_step; JSON_ASSERT(new_indent >= current_indent); return new_indent; } JSON_PRIVATE_UNLESS_TESTED: /*! @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 @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 @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; std::size_t bytes = 0; // number of bytes written to string_buffer // number of bytes written at the point of the last valid byte std::size_t bytes_after_last_accept = 0; std::size_t undumped_chars = 0; for (std::size_t i = 0; i < s.size(); ++i) { // Fast path: at a character boundary (state == UTF8_ACCEPT), // bulk-copy the longest run of bytes that need no escaping using a // SWAR scanner shared with the lexer's contiguous path. The scanner // stops exactly at the first byte dump_escaped would handle // individually, so that byte is left to the byte-at-a-time path // below, keeping escaping output and error diagnostics unchanged. // // - 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. // - 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 = EnsureAscii ? find_ascii_copyable_run(data + i, s.size() - i) : string_bulk_run(data + i, s.size() - i); if (run != 0) { // 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; } put_string(s, i, i + run); bytes_after_last_accept = 0; undumped_chars = 0; i += run; if (i >= s.size()) { break; } } } const auto byte = static_cast(s[i]); switch (decode(state, codepoint, byte)) { case UTF8_ACCEPT: // decode found a new code point { switch (codepoint) { case 0x08: // backspace { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'b'; break; } case 0x09: // horizontal tab { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 't'; break; } case 0x0A: // newline { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'n'; break; } case 0x0C: // formfeed { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'f'; break; } case 0x0D: // carriage return { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'r'; break; } case 0x22: // quotation mark { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\"'; break; } case 0x5C: // reverse solidus { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\\'; break; } default: { // escape control characters (0x00..0x1F) or, if // EnsureAscii parameter is used, non-ASCII characters if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F))) { if (codepoint <= 0xFFFF) { write_u_escape(bytes, static_cast(codepoint)); } else { write_u_escape(bytes, static_cast(0xD7C0u + (codepoint >> 10u))); write_u_escape(bytes, static_cast(0xDC00u + (codepoint & 0x3FFu))); } } else { // copy byte to buffer (all previous bytes // been copied have in default case above) string_buffer[bytes++] = s[i]; } break; } } // 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; } // remember the byte position of this accept bytes_after_last_accept = bytes; undumped_chars = 0; break; } case UTF8_REJECT: // decode found invalid UTF-8 byte { switch (error_handler) { case error_handler_t::strict: { JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_bytes(byte | 0)), nullptr)); } case error_handler_t::ignore: case error_handler_t::replace: { // in case we saw this character the first time, we // would like to read it again, because the byte // may be OK for itself, but just not OK for the // previous sequence if (undumped_chars > 0) { --i; } // reset length buffer to the last accepted index; // thus removing/ignoring the invalid characters bytes = bytes_after_last_accept; if (error_handler == error_handler_t::replace) { // add a replacement character if (EnsureAscii) { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'u'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'd'; } else { string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\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; } bytes_after_last_accept = bytes; } undumped_chars = 0; // continue processing the string state = UTF8_ACCEPT; break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } break; } default: // decode found yet incomplete multibyte code point { if (!EnsureAscii) { // code point will not be escaped - copy byte to buffer string_buffer[bytes++] = s[i]; } ++undumped_chars; break; } } } // we finished processing the string if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) { // write buffer if (bytes > 0) { put_buffer(string_buffer, bytes); } } else { // we finish reading, but do not accept: string was incomplete switch (error_handler) { case error_handler_t::strict: { JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", hex_bytes(static_cast(s.back() | 0))), nullptr)); } case error_handler_t::ignore: { // write all accepted bytes put_buffer(string_buffer, bytes_after_last_accept); break; } case error_handler_t::replace: { // write all accepted bytes put_buffer(string_buffer, bytes_after_last_accept); // add a replacement character if (EnsureAscii) { put_literal("\\ufffd"); } else { put_literal("\xEF\xBF\xBD"); } break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } } } private: /*! @brief append a single character to the write buffer Structural characters ('{', '"', ',', ...) previously went straight to the output adapter, one virtual call each. Buffering them and flushing in bulk turns those many indirect calls into a single memcpy plus an occasional flush, which dominates the cost of serializing object/array-heavy values. */ void put_char(char c) { if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size())) { flush(); } write_buffer[write_buffer_pos++] = c; } /*! @brief append @a indent indentation characters to the write buffer Writes the indentation straight into the buffer instead of copying it out of a pre-grown indentation string, so no auxiliary string has to be sized, resized, or kept in sync with the deepest nesting level reached. An indentation wider than the buffer is emitted by filling the buffer with the indentation character once and flushing that same content repeatedly: flushing does not disturb what the buffer holds, so re-filling it between flushes would be redundant work. */ void put_indent(std::size_t indent) { // closing braces at the outermost level ask for no indentation at all if (indent == 0) { return; } const std::size_t capacity = write_buffer.size(); // fill whatever room is left in the buffer; this is the whole job // whenever the indentation is narrower than the buffer, which is the // case for every sane indent_step const std::size_t head = (std::min)(indent, capacity - write_buffer_pos); std::memset(write_buffer.data() + write_buffer_pos, indent_char, head); write_buffer_pos += head; indent -= head; if (JSON_HEDLEY_LIKELY(indent == 0)) { return; } // the buffer is full and the remainder spans whole buffer-fulls: flush // what is pending, then fill the buffer with the indentation character // exactly once and hand the same bytes to the adapter as often as needed flush(); std::memset(write_buffer.data(), indent_char, capacity); while (indent >= capacity) { write_buffer_pos = capacity; flush(); indent -= capacity; } // the buffer still holds indentation characters throughout, so the tail // only has to be claimed, not written again write_buffer_pos = indent; } /*! @brief append a string literal to the write buffer The length comes from the array bound rather than a hand-written count, so it cannot drift out of sync with the literal. A literal always fits into the buffer (checked at compile time), so unlike @ref put_chars this needs no write-through path for oversized runs. */ template void put_literal(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { static_assert(N >= 2, "put_literal expects a non-empty string literal"); // the array bound counts the terminating NUL, which is not written constexpr std::size_t length = N - 1; static_assert(length < write_buffer_size, "string literal must fit into the write buffer"); if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size())) { flush(); } std::memcpy(write_buffer.data() + write_buffer_pos, s, length); write_buffer_pos += length; } /*! @brief append the characters of @a str in [@a start, @a end) Keeps the pointer arithmetic and the bounds checking inside the function rather than at the call site, which is all @ref put_chars could offer. */ template void put_string(const StringType& str, std::size_t start, std::size_t end) { JSON_ASSERT(start <= end); JSON_ASSERT(end <= str.size()); put_chars(str.data() + start, end - start); } /*! @brief append the first @a length characters of a fixed-size buffer Same as @ref put_chars, but the buffer carries its own bound, so the length can be checked against it - which a bare pointer plus count cannot do. */ template void put_buffer(const std::array& buffer, std::size_t length) { JSON_ASSERT(length <= N); put_chars(buffer.data(), length); } /*! @brief append @a length characters to the write buffer Runs that do not fit the buffer are written straight through the output adapter (after flushing what is pending), so large string/number payloads are not copied an extra time. The callers all reach this through @ref put_literal, @ref put_buffer or @ref put_string, which each derive the length from something that knows it. */ JSON_HEDLEY_NON_NULL(2) void put_chars(const char* s, std::size_t length) { if (JSON_HEDLEY_UNLIKELY(length >= write_buffer.size())) { flush(); o->write_characters(s, length); return; } if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size())) { flush(); } std::memcpy(write_buffer.data() + write_buffer_pos, s, length); write_buffer_pos += length; } JSON_PRIVATE_UNLESS_TESTED: /*! @brief flush the write buffer to the output adapter Writing zero characters is a well-defined no-op for every output adapter, so the buffered length is passed through unconditionally (no empty-guard branch to leave uncovered). @note dump_escaped() and dump_integer()/dump_float() write into the internal write buffer; callers that invoke them directly (rather than through the public dump()) must call flush() before inspecting the output. */ void flush() { o->write_characters(write_buffer.data(), write_buffer_pos); write_buffer_pos = 0; } private: /*! @brief count digits Count the number of decimal (base 10) digits for an input unsigned integer. @param[in] x unsigned integer number to count its digits @return number of decimal digits */ unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) { if (x < 10) { return n_digits; } if (x < 100) { return n_digits + 1; } if (x < 1000) { return n_digits + 2; } if (x < 10000) { return n_digits + 3; } x = x / 10000u; n_digits += 4; } } /*! * @brief convert a byte to a uppercase hex representation * @param[in] byte byte to represent * @return representation ("00".."FF") */ static std::string hex_bytes(std::uint8_t byte) { std::string result = "FF"; constexpr const char* nibble_to_hex = "0123456789ABCDEF"; result[0] = nibble_to_hex[byte / 16]; result[1] = nibble_to_hex[byte % 16]; return result; } /*! * @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer * * 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 * `snprintf`. Advances @a pos by the number of bytes written (6). * * @param[in] pos position in @a string_buffer to write at; there must * be at least 6 bytes of headroom * @param[in] codeunit 16-bit value to encode */ 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]; pos += 6; } // templates to avoid warnings about useless casts template ::value, int> = 0> bool is_negative_number(NumberType x) { return x < 0; } template < typename NumberType, enable_if_t ::value, int > = 0 > bool is_negative_number(NumberType /*unused*/) { 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); std::size_t pos = write_buffer_pos; if (byte >= 100) { write_buffer[pos++] = static_cast('0' + (byte / 100)); write_buffer[pos++] = static_cast('0' + ((byte / 10) % 10)); } else if (byte >= 10) { write_buffer[pos++] = static_cast('0' + (byte / 10)); } write_buffer[pos++] = static_cast('0' + (byte % 10)); write_buffer_pos = pos; } /*! @brief dump an integer Dump a given integer to output stream @a o. Works internally with @a number_buffer. @param[in] x integer number (signed or unsigned) to dump @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < std::is_integral::value || std::is_same::value || std::is_same::value || std::is_same::value, int > = 0 > void dump_integer(NumberType x) { static constexpr std::array, 100> digits_to_99 { { {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, } }; // special case for "0" if (x == 0) { put_char('0'); return; } // use a pointer to fill the buffer auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) number_unsigned_t abs_value; unsigned int n_chars{}; if (is_negative_number(x)) { *buffer_ptr = '-'; abs_value = remove_sign(static_cast(x)); // account one more byte for the minus sign n_chars = 1 + count_digits(abs_value); } else { abs_value = static_cast(x); n_chars = count_digits(abs_value); } // spare 1 byte for '\0' JSON_ASSERT(n_chars < number_buffer.size() - 1); // jump to the end to generate the string from backward, // so we later avoid reversing the result buffer_ptr += static_cast(n_chars); // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg while (abs_value >= 100) { const auto digits_index = static_cast((abs_value % 100)); abs_value /= 100; *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } if (abs_value >= 10) { const auto digits_index = static_cast(abs_value); *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } else { *(--buffer_ptr) = static_cast('0' + abs_value); } put_buffer(number_buffer, n_chars); } /*! @brief dump a floating-point number Dump a given floating-point number to output stream @a o. Works internally with @a number_buffer. @param[in] x floating-point number to dump */ void dump_float(number_float_t x) { // NaN / inf if (!std::isfinite(x)) { put_literal("null"); return; } // If number_float_t is an IEEE-754 single or double precision number, // use the Grisu2 algorithm to produce short numbers which are // guaranteed to round-trip, using strtof and strtod, resp. // // NB: The test below works if == . static constexpr bool is_ieee_single_or_double = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); dump_float(x, std::integral_constant()); } void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { auto* begin = number_buffer.data(); auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); put_buffer(number_buffer, static_cast(end - begin)); } JSON_HEDLEY_NON_NULL(1) static int snprintf_float(char* buf, std::size_t size, int d, double x) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) return (std::snprintf)(buf, size, "%.*g", d, x); } JSON_HEDLEY_NON_NULL(1) static int snprintf_float(char* buf, std::size_t size, int d, long double x) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) return (std::snprintf)(buf, size, "%.*Lg", d, x); } void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // get the number of digits for a float -> text -> float round-trip static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion std::ptrdiff_t len = snprintf_float(number_buffer.data(), number_buffer.size(), d, x); // negative value indicates an error JSON_ASSERT(len > 0); // check if the buffer was large enough JSON_ASSERT(static_cast(len) < number_buffer.size()); // erase thousands separators if (thousands_sep != '\0') { // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081 const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep); std::fill(end, number_buffer.end(), '\0'); JSON_ASSERT((end - number_buffer.begin()) <= len); len = (end - number_buffer.begin()); } // convert decimal point to '.' if (decimal_point != '\0' && decimal_point != '.') { // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081 const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); if (dec_pos != number_buffer.end()) { *dec_pos = '.'; } } put_buffer(number_buffer, static_cast(len)); // determine if we need to append ".0" const bool value_is_int_like = std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, [](char c) { return c == '.' || c == 'e'; }); if (value_is_int_like) { put_literal(".0"); } } /*! @brief check whether a string is UTF-8 encoded The function checks each byte of a string whether it is UTF-8 encoded. The result of the check is stored in the @a state parameter. The function must be called initially with state 0 (accept). State 1 means the string must be rejected, because the current byte is not allowed. If the string is completely processed, but the state is non-zero, the string ended prematurely; that is, the last byte indicated more bytes should have followed. @param[in,out] state the state of the decoding @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in] byte next byte to decode @return new state @note The function has been edited: a std::array is used. @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept { static const std::array utf8d = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 } }; JSON_ASSERT(static_cast(byte) < utf8d.size()); const std::uint8_t type = utf8d[byte]; codep = (state != UTF8_ACCEPT) ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); const std::size_t index = 256u + (static_cast(state) * 16u) + static_cast(type); JSON_ASSERT(index < utf8d.size()); state = utf8d[index]; return state; } /* * Overload to make the compiler happy while it is instantiating * dump_integer for number_unsigned_t. * Must never be called. */ number_unsigned_t remove_sign(number_unsigned_t x) { JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE return x; // LCOV_EXCL_LINE } /* * Helper function for dump_integer * * This function takes a negative signed integer and returns its absolute * value as an unsigned integer. The plus/minus shuffling is necessary as we * cannot directly remove the sign of an arbitrary signed integer as the * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) return static_cast(-(x + 1)) + 1; } private: /// the output of the serializer output_adapter_t o = nullptr; /// a (hopefully) large enough character buffer std::array number_buffer{{}}; /// the locale const std::lconv* loc = nullptr; /// the locale's thousand separator character const char thousands_sep = '\0'; /// the locale's decimal point character const char decimal_point = '\0'; /// string buffer std::array string_buffer{{}}; /// the indentation character const char indent_char; /// error_handler how to react on decoding errors const error_handler_t error_handler; /// buffer collecting output before it is flushed to the output adapter, so /// that the many small structural writes become few bulk writes static constexpr std::size_t write_buffer_size = 1024; std::array write_buffer{{}}; /// number of valid bytes currently held in @ref write_buffer std::size_t write_buffer_pos = 0; }; } // namespace detail NLOHMANN_JSON_NAMESPACE_END