diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index f3868f594..6ae6820f5 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -406,6 +406,7 @@ using array_t = ArrayType>; | `#!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`) | | 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 diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index 2bfa2d2c0..28e1d647a 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -47,6 +47,7 @@ class output_buffer : m_out(sized(out, estimate)) , m_pos(&m_out[0]) , m_end(m_pos + m_out.size()) + , m_reserved((std::max)(estimate, m_out.size())) {} void finish() @@ -116,11 +117,12 @@ class output_buffer 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()) + // 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_reserved), used + n + 256); + 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_pos = &m_out[0] + used; @@ -130,6 +132,9 @@ class output_buffer StringType& m_out; char* m_pos; 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 diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index e24f125bf..6bb6ec771 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -8511,6 +8511,7 @@ class output_buffer : m_out(sized(out, estimate)) , m_pos(&m_out[0]) , m_end(m_pos + m_out.size()) + , m_reserved((std::max)(estimate, m_out.size())) {} void finish() @@ -8580,11 +8581,12 @@ class output_buffer 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()) + // 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_reserved), used + n + 256); + 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_pos = &m_out[0] + used; @@ -8594,6 +8596,9 @@ class output_buffer StringType& m_out; char* m_pos; 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 diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index 75feb74e8..df516d92f 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -10,6 +10,7 @@ #include "doctest_compatibility.h" #include +#include #include #include @@ -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; + using alt_editable_document = nlohmann::basic_json_document; + + const char* text = "{\"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