diff --git a/include/nlohmann/detail/view/materialize.hpp b/include/nlohmann/detail/view/materialize.hpp index b15bec551..deebbc098 100644 --- a/include/nlohmann/detail/view/materialize.hpp +++ b/include/nlohmann/detail/view/materialize.hpp @@ -8,7 +8,7 @@ #pragma once -#include // max +#include // array #include // size_t #include // int64_t, uint8_t #include // string @@ -44,8 +44,9 @@ BasicJsonType materialize(const document_data& d, const node* n) using nav = navigation; // the open containers: their next element (or key of the next member), - // their end, and whether they are objects; sized once from the - // document's deepest nesting (for a subtree an upper bound) + // their end, and whether they are objects; for the first 16 levels on the + // stack, else sized once from the document's deepest nesting (for a + // subtree an upper bound) struct frame { const node* pos; @@ -53,10 +54,43 @@ BasicJsonType materialize(const document_data& d, const node* n) bool object; }; + // a scalar needs neither the handler nor the stack of open containers + switch (static_cast(n->kind)) + { + case value_t::string: + return BasicJsonType(string_t(d.str(*n), n->len)); + case value_t::number_integer: + return BasicJsonType(static_cast(static_cast(integer_bits(*n)))); + case value_t::number_unsigned: + return BasicJsonType(static_cast(integer_bits(*n))); + case value_t::number_float: + return BasicJsonType(float_value(d, *n)); + case value_t::boolean: + return BasicJsonType((n->flags & node_flags::is_true) != 0); + case value_t::null: + return BasicJsonType(nullptr); + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + break; + } + BasicJsonType result; sax_t sax(result, true); const string_t no_token{}; - std::vector open((std::max)(d.max_depth, std::size_t{1})); + // (shallow subtrees on the stack: a call for a small value then allocates only for the value) + std::array shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open + std::vector deep{}; + frame* open = shallow.data(); + std::size_t capacity = shallow.size(); + if (d.max_depth > capacity) + { + deep.resize(d.max_depth); + open = deep.data(); + capacity = deep.size(); + } std::size_t depth = 0; for (;;) { @@ -76,9 +110,13 @@ BasicJsonType materialize(const document_data& d, const node* n) { sax.start_array(n->len); } - if (NLOHMANN_VIEW_UNLIKELY(depth == open.size())) + if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) { - open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits) + // (deeper than the recorded nesting, e.g. after edits) + deep.assign(open, open + depth); + deep.resize(depth * 2); + open = deep.data(); + capacity = deep.size(); } open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; break; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 5e055fd7b..527062a72 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -5778,7 +5778,7 @@ NLOHMANN_JSON_NAMESPACE_END -#include // max +#include // array #include // size_t #include // int64_t, uint8_t #include // string @@ -5818,8 +5818,9 @@ BasicJsonType materialize(const document_data& d, const node* n) using nav = navigation; // the open containers: their next element (or key of the next member), - // their end, and whether they are objects; sized once from the - // document's deepest nesting (for a subtree an upper bound) + // their end, and whether they are objects; for the first 16 levels on the + // stack, else sized once from the document's deepest nesting (for a + // subtree an upper bound) struct frame { const node* pos; @@ -5827,10 +5828,43 @@ BasicJsonType materialize(const document_data& d, const node* n) bool object; }; + // a scalar needs neither the handler nor the stack of open containers + switch (static_cast(n->kind)) + { + case value_t::string: + return BasicJsonType(string_t(d.str(*n), n->len)); + case value_t::number_integer: + return BasicJsonType(static_cast(static_cast(integer_bits(*n)))); + case value_t::number_unsigned: + return BasicJsonType(static_cast(integer_bits(*n))); + case value_t::number_float: + return BasicJsonType(float_value(d, *n)); + case value_t::boolean: + return BasicJsonType((n->flags & node_flags::is_true) != 0); + case value_t::null: + return BasicJsonType(nullptr); + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + break; + } + BasicJsonType result; sax_t sax(result, true); const string_t no_token{}; - std::vector open((std::max)(d.max_depth, std::size_t{1})); + // (shallow subtrees on the stack: a call for a small value then allocates only for the value) + std::array shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open + std::vector deep{}; + frame* open = shallow.data(); + std::size_t capacity = shallow.size(); + if (d.max_depth > capacity) + { + deep.resize(d.max_depth); + open = deep.data(); + capacity = deep.size(); + } std::size_t depth = 0; for (;;) { @@ -5850,9 +5884,13 @@ BasicJsonType materialize(const document_data& d, const node* n) { sax.start_array(n->len); } - if (NLOHMANN_VIEW_UNLIKELY(depth == open.size())) + if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) { - open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits) + // (deeper than the recorded nesting, e.g. after edits) + deep.assign(open, open + depth); + deep.resize(depth * 2); + open = deep.data(); + capacity = deep.size(); } open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; break; diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 670eb3465..74ca74ef2 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -185,6 +185,29 @@ TEST_CASE("json_view") // duplicate keys: the last value, at the position of the first key CHECK(materialized(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})")); CHECK(materialized(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})"); + // scalars (made without the handler), and nesting around the depth up to which the stack of open containers is local + for (const char* scalar : + {"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", "\"\\u00e4\\n\"" + }) + { + CAPTURE(scalar) + CHECK(materialized(scalar) == json::parse(scalar)); + const std::string in_array = std::string("[1,") + scalar + "]"; + const json_document d = json_document::parse(in_array); + CHECK(d.root()[1].materialize() == json::parse(scalar)); + CHECK(d.root()[1].get() == json::parse(scalar)); + } + for (const std::size_t levels : + { + std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{32}, std::size_t{33} + }) + { + CAPTURE(levels) + const std::string nested = std::string(levels, '[') + "{\"k\":[1,{\"j\":2}]}" + std::string(levels, ']'); + CHECK(materialized(nested) == json::parse(nested)); + const std::string wide = "{\"a\":" + nested + ",\"b\":" + nested + "}"; + CHECK(materialized(wide) == json::parse(wide)); + } // very deep nesting (iterative, as parse()) const std::string deep = std::string(100000, '[') + std::string(100000, ']'); // (one check per step, so that a failure shows which step it is) diff --git a/tests/src/unit-json_view_edit.cpp b/tests/src/unit-json_view_edit.cpp index da3f8a29b..4546ad05b 100644 --- a/tests/src/unit-json_view_edit.cpp +++ b/tests/src/unit-json_view_edit.cpp @@ -703,6 +703,27 @@ TEST_CASE("json_view edits: arrays whose elements have no subtrees") check(); } +TEST_CASE("json_view edits: materializing values nested deeper than the document was parsed") +{ + // the stack of open containers of materialize() is sized from the nesting of the parsed text; + // a value added by an edit can be deeper than that (and than the stack that is local to the call) + json_editable_document d = json_editable_document::parse("[1,[2]]"); + json expected = json::array({1, json::array({2})}); + json nested = 3; + for (int level = 0; level < 40; ++level) + { + nested = json::array({nested, json{{"k", level}}}); + } + d.push_back(d.root(), nested); + expected.push_back(nested); + CHECK(d.root().materialize() == expected); + CHECK(d.root()[2].materialize() == nested); + d.set(d.root()[1][0], nested); + expected[1][0] = nested; + CHECK(d.root().materialize() == expected); + CHECK(d.root().get() == expected); +} + namespace { // reads of "a" in an object that repeats it: the first member wins, however the