diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 27ce3de37..e73ca355a 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -283,14 +283,8 @@ class builder const std::size_t start = arena_used(); char* w = m_arena_write; const char* w_end = m_arena_end; -#define NLOHMANN_VIEW_ENSURE(n) \ - if (NLOHMANN_VIEW_UNLIKELY(static_cast(w_end - w) < (n))) \ - { \ - m_arena_write = w; \ - arena_grow(n); \ - w = m_arena_write; \ - w_end = m_arena_end; \ - } +#define NLOHMANN_VIEW_ENSURE(n) \ + if (NLOHMANN_VIEW_UNLIKELY(static_cast(w_end - w) < (n))) { m_arena_write = w; arena_grow(n); w = m_arena_write; w_end = m_arena_end; } { const auto n = static_cast(p - s); if (n <= 16 && end - s >= 16) diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 31e37d9c0..86481d884 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -30,6 +30,7 @@ #include // array #include // nullptr_t, size_t // IWYU pragma: keep #include // uint8_t, uint32_t +#include // FILE #include // memcpy, strlen #include // istream // IWYU pragma: keep #include // distance, input_iterator_tag, iterator_traits @@ -462,8 +463,7 @@ class basic_json_view { return end(); } - const node* const k = unedited() ? detail::view::find_member(*m_doc, m_node, key.data(), key.size()) - : detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = find_key(key); return k != nullptr ? iterator(m_doc, k, true) : end(); } @@ -480,8 +480,7 @@ class basic_json_view /// whether this is an object with a member with this key bool contains(string_view_t key) const { - return is_object() && (unedited() ? detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr - : detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr); + return is_object() && find_key(key) != nullptr; } bool contains(const char* key) const @@ -822,6 +821,24 @@ class basic_json_view return !navigation::editable() || m_doc->edits == nullptr; } + /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr + NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const + { + return find_key(key, std::integral_constant {}); + } + + NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const + { + // (an editable document without edits is looked up as one that cannot be edited) + return unedited() ? detail::view::find_member(*m_doc, m_node, key.data(), key.size()) + : detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + } + + NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const + { + return detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + } + /// the element of an array at an index below its size NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const { diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 3ca6f7fad..7a3ad2a14 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -30,6 +30,7 @@ #include // array #include // nullptr_t, size_t // IWYU pragma: keep #include // uint8_t, uint32_t +#include // FILE #include // memcpy, strlen #include // istream // IWYU pragma: keep #include // distance, input_iterator_tag, iterator_traits @@ -1473,14 +1474,8 @@ class builder const std::size_t start = arena_used(); char* w = m_arena_write; const char* w_end = m_arena_end; -#define NLOHMANN_VIEW_ENSURE(n) \ - if (NLOHMANN_VIEW_UNLIKELY(static_cast(w_end - w) < (n))) \ - { \ - m_arena_write = w; \ - arena_grow(n); \ - w = m_arena_write; \ - w_end = m_arena_end; \ - } +#define NLOHMANN_VIEW_ENSURE(n) \ + if (NLOHMANN_VIEW_UNLIKELY(static_cast(w_end - w) < (n))) { m_arena_write = w; arena_grow(n); w = m_arena_write; w_end = m_arena_end; } { const auto n = static_cast(p - s); if (n <= 16 && end - s >= 16) @@ -8042,8 +8037,7 @@ class basic_json_view { return end(); } - const node* const k = unedited() ? detail::view::find_member(*m_doc, m_node, key.data(), key.size()) - : detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = find_key(key); return k != nullptr ? iterator(m_doc, k, true) : end(); } @@ -8060,8 +8054,7 @@ class basic_json_view /// whether this is an object with a member with this key bool contains(string_view_t key) const { - return is_object() && (unedited() ? detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr - : detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr); + return is_object() && find_key(key) != nullptr; } bool contains(const char* key) const @@ -8402,6 +8395,24 @@ class basic_json_view return !navigation::editable() || m_doc->edits == nullptr; } + /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr + NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const + { + return find_key(key, std::integral_constant {}); + } + + NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const + { + // (an editable document without edits is looked up as one that cannot be edited) + return unedited() ? detail::view::find_member(*m_doc, m_node, key.data(), key.size()) + : detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + } + + NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const + { + return detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + } + /// the element of an array at an index below its size NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const { diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 74ca74ef2..e87162d98 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -187,7 +187,7 @@ TEST_CASE("json_view") 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\"" + {"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", R"("\u00e4\n")" }) { CAPTURE(scalar) @@ -203,9 +203,13 @@ TEST_CASE("json_view") }) { CAPTURE(levels) - const std::string nested = std::string(levels, '[') + "{\"k\":[1,{\"j\":2}]}" + std::string(levels, ']'); + const std::string nested = std::string(levels, '[') + R"({"k":[1,{"j":2}]})" + std::string(levels, ']'); CHECK(materialized(nested) == json::parse(nested)); - const std::string wide = "{\"a\":" + nested + ",\"b\":" + nested + "}"; + std::string wide = R"({"a":)"; + wide += nested; + wide += R"(,"b":)"; + wide += nested; + wide += "}"; CHECK(materialized(wide) == json::parse(wide)); } // very deep nesting (iterative, as parse()) @@ -444,7 +448,7 @@ TEST_CASE("json_view") if (long_file != nullptr) { std::string padded = text; - padded.append((4096 - (padded.size() % 4096)) % 4096 + extra, ' '); + padded.append(((4096 - (padded.size() % 4096)) % 4096) + extra, ' '); CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size()); CHECK(std::fseek(long_file, 0, SEEK_SET) == 0); const json_document from_long_file = json_document::parse(long_file); @@ -1061,7 +1065,7 @@ TEST_CASE("json_view element access and iteration") const std::vector texts = { "[]", "[1]", "[1,2,3]", "[1,\"two\",3.5,true,null,-7,18446744073709551615]", "[[]]", "[{}]", "[1,[2],3]", - "[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", "[\"a\\u00e4\",\"b\"]" + "[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", R"(["a\u00e4","b"])" }; for (const std::string& text : texts) { diff --git a/tests/src/unit-json_view_dump.cpp b/tests/src/unit-json_view_dump.cpp index 12c74e167..a9dca8cc3 100644 --- a/tests/src/unit-json_view_dump.cpp +++ b/tests/src/unit-json_view_dump.cpp @@ -617,7 +617,7 @@ TEST_CASE("json_view large objects") std::size_t farthest = 0; for (const std::string& key : keys) { - std::size_t i = static_cast(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1); + std::size_t i = nlohmann::detail::conditional_static_cast(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1); std::size_t distance = 0; while (used[i]) { @@ -639,7 +639,7 @@ TEST_CASE("json_view large objects") std::size_t width = 0; while (pattern[conversion] >= '0' && pattern[conversion] <= '9') { - width = width * 10 + static_cast(pattern[conversion] - '0'); + width = (width * 10) + static_cast(pattern[conversion] - '0'); ++conversion; } const bool hex = pattern[conversion] == 'x'; @@ -659,10 +659,10 @@ TEST_CASE("json_view large objects") }) { CAPTURE(format) - CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); + CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains) CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement); } - std::mt19937_64 rng(7); + std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) for (const std::size_t length : { std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33} @@ -670,7 +670,17 @@ TEST_CASE("json_view large objects") { CAPTURE(length) std::set distinct; - while (distinct.size() < (length == 1 ? 26 : (length == 2 ? 500 : 700))) // (there are 26 and 676 keys of one and two letters) + // (there are 26 and 676 keys of one and two letters) + std::size_t wanted = 700; + if (length == 1) + { + wanted = 26; + } + else if (length == 2) + { + wanted = 500; + } + while (distinct.size() < wanted) { std::string key(length, 'a'); for (char& c : key) @@ -688,7 +698,7 @@ TEST_CASE("json_view large objects") // In a large object, a key is found by comparing its bytes with those of the member the // table points to: up to 16 bytes as two overlapping words, longer ones with memcmp. // Keys of every length from 0 to 40, and keys that differ from them in one byte. - std::mt19937_64 rng(2026); + std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) std::vector keys; std::set distinct; for (std::size_t length = 0; length <= 40; ++length)