From 7b66cdb9d89fdfb66fdaae695b4bb7703cf97fbf Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 18 Oct 2026 10:01:47 +0200 Subject: [PATCH] Address the review of json_view dump() and comparisons From gregmarr's review of #5624: - The output buffer takes string literals without a length. - write_float() names the IEEE single/double condition once, as the library's serializer does. - equal() checks the types first and compares with !=; the reference to the top frame is noted as dangling after pop_back(). - dump.md: one paragraph per case in the notes. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json_view/dump.md | 6 ++- include/nlohmann/detail/view/compare.hpp | 15 ++++--- include/nlohmann/detail/view/serializer.hpp | 41 ++++++++++++-------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_view/dump.md b/docs/mkdocs/docs/api/basic_json_view/dump.md index 7cc0b6b38..aa9a023bb 100644 --- a/docs/mkdocs/docs/api/basic_json_view/dump.md +++ b/docs/mkdocs/docs/api/basic_json_view/dump.md @@ -60,11 +60,13 @@ Linear in the size of the output text. The walk over the subtree is iterative, so the nesting depth it can write is limited by available memory only, not by the call stack -- as for [`materialize()`](materialize.md). -Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md). With -`#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion, +Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md). + +With `#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion, exactly as [`BasicJsonType::dump`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`), and integers are copied from the source text -- already canonical in JSON, so this matches their shortest form too -- except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it. + `#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception for `#!cpp -0` -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more digits than any number type holds (such a literal is itself classified as a float, see diff --git a/include/nlohmann/detail/view/compare.hpp b/include/nlohmann/detail/view/compare.hpp index 9a50590a3..8ca6058f0 100644 --- a/include/nlohmann/detail/view/compare.hpp +++ b/include/nlohmann/detail/view/compare.hpp @@ -201,6 +201,11 @@ class json_side const BasicJsonType* m_json; }; +inline bool is_number(value_t t) noexcept +{ + return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float; +} + /// whether two sides are equal; iterative, so that the nesting depth is /// limited by memory only template @@ -225,8 +230,6 @@ bool equal(const A& a0, const B& b0) { const value_t ta = a.type(); const value_t tb = b.type(); - const bool numbers = (ta == value_t::number_integer || ta == value_t::number_unsigned || ta == value_t::number_float) - && (tb == value_t::number_integer || tb == value_t::number_unsigned || tb == value_t::number_float); if (ta == value_t::discarded || tb == value_t::discarded) { // basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) @@ -237,13 +240,13 @@ bool equal(const A& a0, const B& b0) } else { - if (!numbers && ta != tb) + if (ta != tb && !(is_number(ta) && is_number(tb))) // numbers of different types can be equal { return false; } if (ta == value_t::string) { - if (!(a.string() == b.string())) + if (a.string() != b.string()) { return false; } @@ -272,7 +275,7 @@ bool equal(const A& a0, const B& b0) } stack.push_back(std::move(f)); } - else if (!(a.scalar() == b.scalar())) // numbers (also of different types), null, boolean + else if (a.scalar() != b.scalar()) // numbers (also of different types), null, boolean { return false; } @@ -289,7 +292,7 @@ bool equal(const A& a0, const B& b0) const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size(); if (f.next == count) { - stack.pop_back(); + stack.pop_back(); // (f dangles now, and is bound again after continue) continue; } if (f.object) diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index bde8797c5..4ec902421 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -69,6 +69,13 @@ class output_buffer m_pos += n; } + /// a string literal, without its terminating NUL + template + NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N]) + { + put(s, N - 1); + } + void put_repeated(char c, std::size_t n) { reserve(n); @@ -170,7 +177,7 @@ class view_serializer frame& f = stack.back(); if (f.pos == f.end) { - const bool object = f.object; + const bool object = f.object; // (f dangles after pop_back) stack.pop_back(); newline(stack.size()); m_out.put(object ? '}' : ']'); @@ -187,7 +194,7 @@ class view_serializer write_string(*f.pos); if (m_style.pretty) { - m_out.put(": ", 2); + m_out.put(": "); } else { @@ -220,16 +227,16 @@ class view_serializer switch (static_cast(n.kind)) { case value_t::null: - m_out.put("null", 4); + m_out.put("null"); break; case value_t::boolean: if ((n.flags & node_flags::is_true) != 0) { - m_out.put("true", 4); + m_out.put("true"); } else { - m_out.put("false", 5); + m_out.put("false"); } break; case value_t::string: @@ -274,12 +281,14 @@ class view_serializer { if (!std::isfinite(x)) { - m_out.put("null", 4); + m_out.put("null"); return; } - write_float(x, std::integral_constant < bool, - (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) > {}); + 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*/) @@ -364,25 +373,25 @@ class view_serializer switch (codepoint) { case 0x08: - m_out.put("\\b", 2); + m_out.put("\\b"); return; case 0x09: - m_out.put("\\t", 2); + m_out.put("\\t"); return; case 0x0A: - m_out.put("\\n", 2); + m_out.put("\\n"); return; case 0x0C: - m_out.put("\\f", 2); + m_out.put("\\f"); return; case 0x0D: - m_out.put("\\r", 2); + m_out.put("\\r"); return; case 0x22: - m_out.put("\\\"", 2); + m_out.put("\\\""); return; case 0x5C: - m_out.put("\\\\", 2); + m_out.put("\\\\"); return; default: break;