Compare commits

...
Author SHA1 Message Date
Claude 36da0e2f78 Use a raw string literal in the alt-string json_view test
clang-tidy (modernize-raw-string-literal) rejects the escaped literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>
2026-11-02 07:20:25 +00:00
Niels Lohmann 9da5272f6c json_view: dump() without StringType::capacity() (#5994)
output_buffer::grow() compared the output size with m_out.capacity(),
which the StringType requirements do not include, so dump() did not
compile with the tests' alt_string. Keep what was last reserved in the
buffer itself and use it as a lower bound of the capacity instead.

Add json_view tests with alt_string (read-only and editable documents,
dump with several growth steps, save/load), and document reserve(n) and
resize(n) as the StringType requirements of basic_json_view::dump.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-01 22:48:43 +01:00
4 changed files with 72 additions and 8 deletions

No files matched your search

@@ -406,6 +406,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` | | `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) | | [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
| exception messages | `data()` and `size()`, or `begin()` and `end()` | | exception messages | `data()` and `size()`, or `begin()` and `end()` |
| [`basic_json_view::dump`](../../api/basic_json_view/dump.md) | `reserve(n)` and `resize(n)` (one argument); `capacity()` is **not** required |
### Compatible types ### Compatible types
+9 -4
View File
@@ -47,6 +47,7 @@ class output_buffer
: m_out(sized(out, estimate)) : m_out(sized(out, estimate))
, m_pos(&m_out[0]) , m_pos(&m_out[0])
, m_end(m_pos + m_out.size()) , m_end(m_pos + m_out.size())
, m_reserved((std::max)(estimate, m_out.size()))
{} {}
void finish() void finish()
@@ -116,11 +117,12 @@ class output_buffer
const auto used = static_cast<std::size_t>(m_pos - m_out.data()); const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good // (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 // 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) // lower bound used + n + 256 can exceed m_reserved, and then it must win)
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256); const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
if (size > m_out.capacity()) if (size > m_reserved)
{ {
m_out.reserve((std::max)(m_out.capacity() * 2, size)); m_reserved = (std::max)(m_reserved * 2, size);
m_out.reserve(m_reserved);
} }
m_out.resize(size); m_out.resize(size);
m_pos = &m_out[0] + used; m_pos = &m_out[0] + used;
@@ -130,6 +132,9 @@ class output_buffer
StringType& m_out; StringType& m_out;
char* m_pos; char* m_pos;
char* m_end; char* m_end;
/// what was last passed to reserve(), a lower bound of the capacity
/// (StringType need not have a capacity() member)
std::size_t m_reserved;
}; };
/// The length of the run at s that dump() writes unchanged without /// The length of the run at s that dump() writes unchanged without
+9 -4
View File
@@ -8511,6 +8511,7 @@ class output_buffer
: m_out(sized(out, estimate)) : m_out(sized(out, estimate))
, m_pos(&m_out[0]) , m_pos(&m_out[0])
, m_end(m_pos + m_out.size()) , m_end(m_pos + m_out.size())
, m_reserved((std::max)(estimate, m_out.size()))
{} {}
void finish() void finish()
@@ -8580,11 +8581,12 @@ class output_buffer
const auto used = static_cast<std::size_t>(m_pos - m_out.data()); const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good // (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 // 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) // lower bound used + n + 256 can exceed m_reserved, and then it must win)
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256); const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
if (size > m_out.capacity()) if (size > m_reserved)
{ {
m_out.reserve((std::max)(m_out.capacity() * 2, size)); m_reserved = (std::max)(m_reserved * 2, size);
m_out.reserve(m_reserved);
} }
m_out.resize(size); m_out.resize(size);
m_pos = &m_out[0] + used; m_pos = &m_out[0] + used;
@@ -8594,6 +8596,9 @@ class output_buffer
StringType& m_out; StringType& m_out;
char* m_pos; char* m_pos;
char* m_end; char* m_end;
/// what was last passed to reserve(), a lower bound of the capacity
/// (StringType need not have a capacity() member)
std::size_t m_reserved;
}; };
/// The length of the run at s that dump() writes unchanged without /// The length of the run at s that dump() writes unchanged without
+53
View File
@@ -10,6 +10,7 @@
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/json_view.hpp>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
@@ -483,4 +484,56 @@ TEST_CASE("alternative string type")
} }
} }
TEST_CASE("alternative string type with json_view")
{
// regression test for #5994: basic_json_view::dump() called
// StringType::capacity(), which alt_string (and the documented StringType
// requirements) do not provide
using alt_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::read_only>;
using alt_editable_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::editable>;
const char* text = R"({"a":[1,"x\u00e4"],"b":{"c":true,"d":null},"e":2.5})";
const alt_json j = alt_json::parse(text);
SECTION("read-only document")
{
const alt_document d = alt_document::parse(text);
CHECK(d.root().dump() == j.dump());
CHECK(d.root().dump(2) == j.dump(2));
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
CHECK(d.root().materialize() == j);
CHECK(d.root().contains(alt_string("b")));
CHECK(d.root()[alt_string("b")][alt_string("c")].dump() == "true");
}
SECTION("long output")
{
// more than one growth step of the output buffer
alt_json big = alt_json::array();
for (int i = 0; i < 20000; ++i)
{
big.push_back(alt_json::parse(text));
}
const alt_string big_text = big.dump();
const alt_document d = alt_document::parse(big_text);
CHECK(d.root().dump() == big_text);
CHECK(d.root().dump(4) == big.dump(4));
}
SECTION("editable document")
{
alt_editable_document d = alt_editable_document::parse(text);
d.set(d.root(), "f", "new");
d.push_back(d.root()[alt_string("a")], 3);
alt_json expected = j;
expected["f"] = "new";
expected["a"].push_back(3);
CHECK(d.root().materialize() == expected);
CHECK(alt_json::parse(d.root().dump()) == expected);
const alt_editable_document loaded = alt_editable_document::load(d.save());
CHECK(loaded.root().materialize() == expected);
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_CLANG_SUPPRESS_WARNING_POP