// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #pragma once #include // max #include // array #include // isfinite #include // size_t #include // uint8_t, uint32_t #include // memcpy, memset #include // numeric_limits #include // integral_constant #include // vector #include #include #include #include #include #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { namespace view { /// append-only output buffer: writes through a raw pointer into a string that /// is resized ahead, and trimmed by finish(). The estimate is reserved, and the /// string grows in steps of 64 KiB within it: resize() fills the new bytes /// with zeros (before C++23, a string cannot grow without), and a small step /// is filled while the writer is about to use it, in the cache, instead of /// filling the whole estimate in memory first. template class output_buffer { public: output_buffer(StringType& out, std::size_t estimate) : m_out(sized(out, estimate)) , m_pos(&m_out[0]) , m_end(m_pos + m_out.size()) {} void finish() { const auto size = static_cast(m_pos - m_out.data()); m_out.resize(size); } NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n) { if (NLOHMANN_VIEW_UNLIKELY(static_cast(m_end - m_pos) < n)) { grow(n); } } NLOHMANN_VIEW_ALWAYS_INLINE void put(char c) { reserve(1); *m_pos++ = c; } NLOHMANN_VIEW_ALWAYS_INLINE void put(const char* s, std::size_t n) { reserve(n); std::memcpy(m_pos, s, n); m_pos += n; } /// a string literal, without its terminating NUL template NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { put(s, N - 1); } /// the write position and the end of the writable space, for a writer /// that keeps the position in a local variable (set_cursor() hands it back) char* cursor() const noexcept { return m_pos; } char* limit() const noexcept { return m_end; } void set_cursor(char* p) noexcept { m_pos = p; } private: /// the size of a growth step static constexpr std::size_t step = 0x10000; static StringType& sized(StringType& out, std::size_t estimate) { out.reserve(estimate); out.resize(detail::clamp(estimate, std::size_t{64}, step)); return out; } NLOHMANN_VIEW_NOINLINE void grow(std::size_t n) { const auto used = static_cast(m_pos - m_out.data()); // (a step does not go beyond the reserved estimate, so that a good // estimate is never copied to a larger allocation; not a clamp: the // lower bound used + n + 256 can exceed capacity(), and then it must win) const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256); if (size > m_out.capacity()) { m_out.reserve((std::max)(m_out.capacity() * 2, size)); } m_out.resize(size); m_pos = &m_out[0] + used; m_end = &m_out[0] + m_out.size(); } StringType& m_out; char* m_pos; char* m_end; }; /// The length of the run at s that dump() writes unchanged without /// ensure_ascii: all bytes but quotes, backslashes, and control characters. /// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the /// strings of a document are valid UTF-8 (a damaged image loaded with /// image_check::bounds can have others, which are then written unchanged). inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept { std::size_t i = 0; #if NLOHMANN_VIEW_SSE2 // (16 bytes per step; with NEON, the mask of a step costs more than it saves on the // short runs that are typical here) // NOLINTBEGIN(portability-simd-intrinsics) for (; i + 16 <= n; i += 16) { const __m128i in = _mm_loadu_si128(static_cast(static_cast(s + i))); // NOLINT(bugprone-casting-through-void) // (a byte is below 0x20 if it is its own minimum with 0x1F) const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))), _mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in)); const auto mask = static_cast(static_cast(_mm_movemask_epi8(special))); if (mask != 0) { return i + static_cast(count_trailing_zeros(mask)); } } // NOLINTEND(portability-simd-intrinsics) #endif constexpr std::uint64_t ones = 0x0101010101010101ull; constexpr std::uint64_t high = 0x8080808080808080ull; for (; i + 8 <= n; i += 8) { const std::uint64_t v = read_eight_bytes(s + i); const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high; if (stop != 0) { // the lowest flagged byte is the first stop: borrows only flag bytes above a true one return i + (static_cast(count_trailing_zeros(stop)) / 8); } } for (; i < n; ++i) { if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20) { return i; } } return n; } /// Whether dump() with ensure_ascii writes the string at s unchanged, for a /// string of the source without escape sequences (which has no quote, /// backslash, or control character): no byte is DEL (0x7F) or above. Adding 1 /// to each byte sets its high bit exactly for 0x7F, and a carry into the next /// byte only comes from 0xFF, whose own high bit is set anyway. inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept { constexpr std::uint64_t ones = 0x0101010101010101ull; constexpr std::uint64_t high = 0x8080808080808080ull; std::size_t i = 0; for (; i + 8 <= n; i += 8) { const std::uint64_t v = read_eight_bytes(s + i); if (((v | (v + ones)) & high) != 0) { return false; } } for (; i < n; ++i) { if (static_cast(s[i]) >= 0x7F) { return false; } } return true; } #if NLOHMANN_VIEW_VECTOR /// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a /// backslash, a control character, DEL, or not ASCII; a signed compare with /// 0x20 finds control characters and non-ASCII bytes at once), one bit per /// byte (x86) or one nibble per byte (NEON) NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept { #if NLOHMANN_VIEW_NEON const uint8x16_t in = vld1q_u8(p); const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)))); return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0); #else const __m128i in = _mm_loadu_si128(static_cast(static_cast(p))); // NOLINT(bugprone-casting-through-void) const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))), _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20)))); return static_cast(static_cast(_mm_movemask_epi8(special))); #endif } /// the width in bits of a byte in the mask of ascii_stop_mask() constexpr unsigned ascii_stop_bits() noexcept { return NLOHMANN_VIEW_NEON ? 4u : 1u; } #endif /// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the /// buffer that holds the n bytes at p (a short run is read as 16 bytes if /// they are in the buffer). inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept { #if NLOHMANN_VIEW_VECTOR if (n >= 16) { std::size_t i = 0; for (; i + 16 <= n; i += 16) { const std::uint64_t mask = ascii_stop_mask(p + i); if (mask != 0) { return i + (static_cast(count_trailing_zeros(mask)) / ascii_stop_bits()); } } if (i == n) { return n; } // the last 16 bytes: those before i are known to be copyable const std::uint64_t mask = ascii_stop_mask(p + n - 16); return mask != 0 ? n - 16 + (static_cast(count_trailing_zeros(mask)) / ascii_stop_bits()) : n; } if (end - p >= 16) { // (the bytes after the run are masked out) const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u); return mask != 0 ? static_cast(count_trailing_zeros(mask)) / ascii_stop_bits() : n; } #else static_cast(end); #endif return find_ascii_copyable_run(p, n); } /// A stack that starts in a buffer of the caller (a local array) and moves to /// the heap (a vector of the caller) only when that is full, so that dumps of /// shallow documents need no allocation. The top is a pointer, as in /// std::vector. grow() is static and out of line: it gets the vector and the /// used range and returns the new storage, so the address of the stack never /// reaches a call. Otherwise the compiler would have to keep the three /// pointers in memory and load and store them on every push_back() and /// pop_back(), not just when growing. template class small_stack { public: small_stack(T* buffer, std::size_t capacity, std::vector& heap) noexcept : m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(heap) {} small_stack(const small_stack&) = delete; small_stack(small_stack&&) = delete; small_stack& operator=(const small_stack&) = delete; small_stack& operator=(small_stack&&) = delete; ~small_stack() = default; NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x) { if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end)) { const std::size_t used = size(); const std::size_t capacity = 2 * static_cast(m_end - m_begin); m_begin = grow(m_heap, m_begin, used, capacity); m_top = m_begin + used; m_end = m_begin + capacity; } *m_top++ = x; } NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept { return m_top[-1]; } NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept { --m_top; } NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept { return m_top == m_begin; } NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept { return static_cast(m_top - m_begin); } private: /// the used entries moved to heap storage of the given capacity NLOHMANN_VIEW_NOINLINE static T* grow(std::vector& heap, const T* begin, std::size_t used, std::size_t capacity) { std::vector bigger(capacity); std::copy(begin, begin + used, bigger.begin()); heap.swap(bigger); return heap.data(); } T* m_begin; T* m_top; T* m_end; std::vector& m_heap; }; /// a string literal at w, without its terminating NUL (after room()) /// @return a pointer past it template NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexcept // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { std::memcpy(w, s, N - 1); return w + N - 1; } /// the depth of the innermost open container, counted only for indented output template struct depth_counter { std::size_t value = 0; void enter() noexcept { ++value; } std::size_t leave() noexcept { return --value; } std::size_t get() const noexcept { return value; } }; template<> struct depth_counter { void enter() noexcept {} std::size_t leave() noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter { return 0; } std::size_t get() const noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter { return 0; } }; /// how the view's dump() writes a value struct dump_style { bool pretty = false; ///< indent >= 0 std::size_t indent = 0; ///< characters per level char indent_char = ' '; bool ensure_ascii = false; bool source_numbers = false; ///< copy number tokens from the source }; /*! @brief write a view's subtree as basic_json::dump() writes the value The output of a subtree equals ordered_json::parse(text).dump() of it for the same arguments (members in document order): strings are escaped by the same rules, with the library's scanning kernels; floats are written with the library's conversion; integers are copied from the source, where they are canonical (except "-0", which parse() reads as 0). The walk is iterative, so the nesting depth is limited by memory only. */ template class view_serializer { using nav = navigation; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; /// doubles that are IEEE 754 binary64 are written from their bits (Zmij) using fast_double = std::integral_constant < bool, std::is_same::value && ::nlohmann::detail::dtoa_impl::is_binary64::value >; public: view_serializer(const document_data& d, string_t& out, std::size_t estimate, const dump_style& style) : m_doc(d), m_out(out, estimate), m_style(style) {} void dump(const node* root) { if (m_style.ensure_ascii) { dump_ascii(root); } else if (m_style.pretty) { dump_pretty(root); } else if (m_style.source_numbers) { dump_text(root); } else { dump_text(root); } } private: /// dump_text() with ensure_ascii, out of line: the walk without it is /// faster when it is not merged with this one into dump() NLOHMANN_VIEW_NOINLINE void dump_ascii(const node* root) { if (m_style.pretty) { if (m_style.source_numbers) { dump_text(root); } else { dump_text(root); } } else if (m_style.source_numbers) { dump_text(root); } else { dump_text(root); } } /// dump_text() with indentation, out of line: the compact walk is faster /// when it is not merged with the indented ones into dump() NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root) { if (m_style.source_numbers) { dump_text(root); } else { dump_text(root); } } /// a string that is not a plain string of the source (decoded, or written /// by an edit), without ensure_ascii: runs without characters to escape /// are copied. If validate is set, the string must be valid UTF-8: the /// runs are found by the lexer's scan, which validates the sequences, and /// invalid UTF-8 throws what basic_json::dump() throws. (The compact output /// writes the bytes of a damaged image unchanged, and scans without /// validating them.) NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate) { const auto* const s = reinterpret_cast(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) m_out.put('"'); for (std::size_t i = 0; i < n.len;) { const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i); if (run != 0) { m_out.put(reinterpret_cast(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) i += run; continue; } if (validate && s[i] >= 0x80) { invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8) } write_codepoint(s[i], s + i, 1); // a quote, a backslash, or a control character ++i; } m_out.put('"'); } /// whether dump_text() copies a string or key from the source: one of /// the source, and with ensure_ascii one without bytes >= 0x7F (an overload /// each, so that the walk without ensure_ascii is compiled exactly as /// before: with GCC, a condition that only folds away later changed its /// block layout and made it 4-6% slower) static bool copied(const node& n, const char* /*src*/, std::false_type /*ensure_ascii*/) noexcept { return (n.flags & node_flags::storage) == 0; } static bool copied(const node& n, const char* src, std::true_type /*ensure_ascii*/) noexcept { return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len); } /*! @brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX (a character beyond U+FFFF as a surrogate pair); strings of a document are valid UTF-8, except in a damaged image loaded with image_check::bounds, for which this throws what basic_json::dump() throws for the string */ NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n) { const auto* const s = reinterpret_cast(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const std::size_t size = n.len; char* w = m_out.cursor(); char* lim = m_out.limit(); // room for k bytes const auto room = [&](std::size_t k) { const auto left = static_cast(lim - w); if (NLOHMANN_VIEW_UNLIKELY(left < k)) { m_out.set_cursor(w); m_out.reserve(k); w = m_out.cursor(); lim = m_out.limit(); } }; static constexpr const char* hex = "0123456789abcdef"; // \\uXXXX const auto u_escape = [&](std::uint32_t u) { w[0] = '\\'; w[1] = 'u'; w[2] = hex[(u >> 12u) & 0xFu]; w[3] = hex[(u >> 8u) & 0xFu]; w[4] = hex[(u >> 4u) & 0xFu]; w[5] = hex[u & 0xFu]; w += 6; }; room(1); *w++ = '"'; std::size_t i = 0; // (a string of the source starts with a run that has nothing to escape, 16 bytes per step) const std::size_t prefix = (n.flags & node_flags::storage) == 0 ? ascii_copyable_run(s, size, reinterpret_cast(m_doc.src + m_doc.size)) : 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) if (prefix != 0) { room(prefix); std::memcpy(w, s, prefix); w += prefix; i = prefix; } while (i < size) { // (a character that needs an escape sequence is not worth a call) const std::size_t run = is_ascii_copyable(s[i]) ? find_ascii_copyable_run(s + i, size - i) : 0; if (run != 0) { room(run); std::memcpy(w, s + i, run); w += run; i += run; continue; } room(12); if (s[i] >= 0x80) { const unsigned char lead = s[i]; // (the two-byte sequences of Latin text and the three-byte sequences of most other // text, with continuation bytes of any value: the ranges that are restricted (E0, // ED, and the sequences of four bytes) go through validate_one_utf8()) if (lead >= 0xE1 && lead <= 0xEF && lead != 0xED && size - i >= 3 && (s[i + 1] & 0xC0u) == 0x80 && (s[i + 2] & 0xC0u) == 0x80) { u_escape(((lead & 0x0Fu) << 12u) | ((s[i + 1] & 0x3Fu) << 6u) | (s[i + 2] & 0x3Fu)); i += 3; continue; } if (lead >= 0xC2 && lead <= 0xDF && size - i >= 2 && (s[i + 1] & 0xC0u) == 0x80) { u_escape(((lead & 0x1Fu) << 6u) | (s[i + 1] & 0x3Fu)); i += 2; continue; } const std::size_t len = validate_one_utf8(s + i, size - i); if (NLOHMANN_VIEW_UNLIKELY(len == 0)) { m_out.set_cursor(w); invalid_utf8(s, size); return; // LCOV_EXCL_LINE (invalid_utf8 throws) } std::uint32_t codepoint = s[i] & (0xFFu >> (len + 1)); for (std::size_t k = 1; k < len; ++k) { codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu); } i += len; if (codepoint <= 0xFFFF) { u_escape(codepoint); } else { u_escape(0xD7C0u + (codepoint >> 10u)); u_escape(0xDC00u + (codepoint & 0x3FFu)); } continue; } // a quote, a backslash, or a control character (or DEL) const unsigned char c = s[i++]; char escape = 0; switch (c) { case 0x08: escape = 'b'; break; case 0x09: escape = 't'; break; case 0x0A: escape = 'n'; break; case 0x0C: escape = 'f'; break; case 0x0D: escape = 'r'; break; case 0x22: escape = '"'; break; case 0x5C: escape = '\\'; break; default: break; } if (escape != 0) { w[0] = '\\'; w[1] = escape; w += 2; } else { u_escape(c); } } room(1); *w++ = '"'; m_out.set_cursor(w); } /// the copies of dump_text() that are not fixed-size moves (long /// strings, or near the end of the source); out of line, so that the /// compiler does not merge the fixed-size moves into this call NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept { std::memcpy(to, from, n); } /// a compile-time option as a runtime condition: testing the template /// argument directly makes a condition like `Editable && c` constant when /// the option is off, which MSVC reports as C4127 static NLOHMANN_VIEW_ALWAYS_INLINE bool enabled(bool option) noexcept { return option; } /*! @brief the output, compact (the default dump()) or indented, with or without ensure_ascii The same walk as dump(), with the write position in a local variable (stores through char pointers would otherwise force a reload of the buffer's members after each one), and with strings and number tokens of the source copied by fixed-size moves of 32 bytes where the source has that many bytes left, instead of a library call per token. The buffer keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a string of the source is copied only if it has nothing to escape (see copied()); any other string is written by write_ascii(). */ template void dump_text(const node* root) { struct frame { const node* pos; ///< (editable documents) next element, or key of the next member const node* end; bool object; }; std::array buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read std::vector heap; small_stack stack(buffer.data(), buffer.size(), heap); const char* const src = m_doc.src; const char* const src_end = src + m_doc.size; char* w = m_out.cursor(); char* lim = m_out.limit(); // (indented output) a line break and 63 indentation characters, from // which the line break of a level of up to 63 characters is a copy std::array ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used if (Pretty) { ruler.fill(m_style.indent_char); ruler[0] = '\n'; } const std::size_t indent = m_style.indent; depth_counter level; // depth of the innermost open container (indented output) // room for n bytes and the slack const auto room = [&](std::size_t n) { if (NLOHMANN_VIEW_UNLIKELY(static_cast(lim - w) < n + 64)) { m_out.set_cursor(w); m_out.reserve(n + 64); w = m_out.cursor(); lim = m_out.limit(); } }; // copy n bytes of the source (after room(n)) const auto copy = [&](const char* from, std::size_t n) { if (n <= 32 && static_cast(src_end - from) >= 32) { std::memcpy(w, from, 32); } else if (n <= 256 && static_cast(src_end - from) >= n + 32) { for (std::size_t i = 0; i < n; i += 32) { std::memcpy(w + i, from + i, 32); } } else { copy_long(w, from, n); } w += n; }; // (indented output) the line break and the indentation of a level const auto newline = [&](std::size_t depth) { const std::size_t count = 1 + (depth * indent); room(count); if (count <= ruler.size()) { std::memcpy(w, ruler.data(), ruler.size()); } else { *w = '\n'; std::memset(w + 1, m_style.indent_char, count - 1); } w += count; }; // a string that is not a plain string of the source (out of line, so // that the cursor stays in a register here) const auto escaped = [&](const node & n) { m_out.set_cursor(w); if (enabled(Ascii)) { write_ascii(n); } else { write_decoded(n, Pretty); } w = m_out.cursor(); lim = m_out.limit(); }; // Read-only documents: the elements of a container follow it in the // node array, so the walk goes through the array in order, and a // frame only needs the end of its container. Editable documents: the // elements of a moved container live elsewhere, so a frame keeps the // position of the next element (see navigation). // The innermost open container is kept in registers (cur; end == // nullptr: none), the stack holds the ones around it. frame cur{nullptr, nullptr, false}; const node* n = root; for (;;) { // write the value at n (read-only documents: and advance n) bool opened = false; // false positive: n comes from nav::value(), which never returns null for a valid index // @infer-ignore NULLPTR_DEREFERENCE switch (static_cast(n->kind)) { case value_t::string: if (copied(*n, src, std::integral_constant())) { room(n->len + 2); *w++ = '"'; copy(src + n->off, n->len); *w++ = '"'; } else { escaped(*n); } break; case value_t::number_integer: case value_t::number_unsigned: { const std::uint32_t len = number_length(*n); room(len); if (enabled(Editable) && (n->flags & node_flags::storage) != 0) { copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit w += len; break; } const char* const token = src + n->off; if (!enabled(SourceNumbers) && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0')) { *w++ = '0'; // parse() reads -0 as the integer 0 } else { copy(token, len); } break; } case value_t::number_float: if (enabled(SourceNumbers) && (n->flags & node_flags::storage) != node_flags::edited) { room(n->len); copy(src + n->off, n->len); } else if (enabled(fast_double::value)) { room(64); w = write_double_at(w, *n); } else { m_out.set_cursor(w); write_float_node(*n); w = m_out.cursor(); lim = m_out.limit(); } break; case value_t::boolean: room(8); if ((n->flags & node_flags::is_true) != 0) { w = put_literal(w, "true"); } else { w = put_literal(w, "false"); } break; case value_t::object: case value_t::array: { const bool object = n->kind == static_cast(value_t::object); room(8); if (n->len == 0) { w = object ? put_literal(w, "{}") : put_literal(w, "[]"); } else { *w++ = object ? '{' : '['; stack.push_back(cur); level.enter(); if (Editable) { cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object}; } else { cur = frame{nullptr, n + n->next, object}; } opened = true; } break; } case value_t::null: room(8); w = put_literal(w, "null"); break; case value_t::binary: // LCOV_EXCL_LINE (not in a document) case value_t::discarded: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE break; // LCOV_EXCL_LINE } if (!Editable) { ++n; // the next node: the first element of an opened container, or the node after a scalar } // go to the next value: close finished containers, then separate // (a container just opened has an element) if (!opened) { for (;;) { if (cur.end == nullptr) { m_out.set_cursor(w); m_out.finish(); return; } if ((Editable ? cur.pos : n) != cur.end) { break; } if (Pretty) { newline(level.leave()); } room(1); *w++ = cur.object ? '}' : ']'; cur = stack.back(); stack.pop_back(); } room(1); *w++ = ','; } if (Pretty) { newline(level.get()); } const node* const at = Editable ? cur.pos : n; if (cur.object) { const node& key = *at; if (copied(key, src, std::integral_constant())) { room(key.len + (Pretty ? 4 : 3)); *w++ = '"'; copy(src + key.off, key.len); w[0] = '"'; w[1] = ':'; w += 2; } else { escaped(key); room(Pretty ? 2 : 1); *w++ = ':'; } if (Pretty) { *w++ = ' '; } if (Editable) { n = nav::value(at + 1); cur.pos = document_data::after(at + 1); } else { ++n; } } else if (Editable) { n = nav::value(at); cur.pos = document_data::after(at); } } } /// a float node as dump() writes it void write_float_node(const node& n) { write_float_node(n, fast_double {}); } void write_float_node(const node& n, std::false_type /*other*/) { write_float(float_value(m_doc, n)); } void write_float_node(const node& n, std::true_type /*double*/) { m_out.reserve(64); m_out.set_cursor(write_double_at(m_out.cursor(), n)); } /*! @brief (doubles) the float at n as dump() writes it, at w (64 bytes of room) A token of at most 15 significant digits is written from its digits, without a conversion: two decimals of at most 15 digits are farther apart than the rounding interval of a (normal) double (the argument behind DBL_DIG), so the token's digits are the shortest ones of its double, which the library's conversion writes (Zmij). Other tokens are converted from the digits already read. */ char* write_double_at(char* w, const node& n) { const unsigned int_digits = n.extra & 0xFFu; const unsigned frac_digits = n.extra >> 8u; if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19) { const auto* const first = reinterpret_cast(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) // (the exponent keeps the value far from subnormals and overflow) if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290) { *w = '-'; w += d.negative ? 1 : 0; // (without leading zeros, all digits of the token count; the // check also keeps an image that was only checked for bounds, // whose token may not be made of digits, from the counted // overload) const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten(); const unsigned count = int_digits + frac_digits; return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count] ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast(count), static_cast(d.exponent)) : ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast(d.exponent)); } return write_double_value_at(w, decimal_to_float(d)); // (without reading the token again) } return write_double_value_at(w, static_cast(float_value(m_doc, n))); } /// n bytes of text at w static char* write_text_at(char* w, const char* text, std::size_t n) noexcept { std::memcpy(w, text, n); return w + n; } /// a double as dump() writes it, at w (64 bytes of room) static char* write_double_value_at(char* w, double x) { // (from the bits: without the checks of to_chars()) auto bits = bit_cast(x); if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u)) { return write_text_at(w, "null", 4); } *w = '-'; w += bits >> 63u; bits &= ~(std::uint64_t{1} << 63u); if (bits == 0) { return write_text_at(w, "0.0", 3); } return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits)); } /// as serializer::dump_float() void write_float(number_float_t x) { if (!std::isfinite(x)) { m_out.put("null"); return; } 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::digits == 53 && std::numeric_limits::max_exponent == 1024)); write_float(x, std::integral_constant()); } void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { std::array buf{}; const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x); m_out.put(buf.data(), static_cast(end - buf.data())); } void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // other types (e.g. long double) are rare: the library writes them const string_t s = BasicJsonType(x).dump(); m_out.put(s.data(), s.size()); } /// throw what basic_json::dump() throws for a string that is not valid UTF-8 NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n) { const string_t dumped = BasicJsonType(string_t(reinterpret_cast(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) static_cast(dumped); } template void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len) { switch (codepoint) { case 0x08: m_out.put("\\b"); return; case 0x09: m_out.put("\\t"); return; case 0x0A: m_out.put("\\n"); return; case 0x0C: m_out.put("\\f"); return; case 0x0D: m_out.put("\\r"); return; case 0x22: m_out.put("\\\""); return; case 0x5C: m_out.put("\\\\"); return; default: break; } if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F)) { if (codepoint <= 0xFFFF) { write_u_escape(codepoint); } else { write_u_escape(0xD7C0u + (codepoint >> 10u)); write_u_escape(0xDC00u + (codepoint & 0x3FFu)); } return; } m_out.put(reinterpret_cast(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs) } void write_u_escape(std::uint32_t u) { static constexpr const char* hex = "0123456789abcdef"; const std::array e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}}; m_out.put(e.data(), e.size()); } const document_data& m_doc; output_buffer m_out; const dump_style m_style; }; } // namespace view } // namespace detail NLOHMANN_JSON_NAMESPACE_END