diff --git a/BUILD.bazel b/BUILD.bazel index 86f420279..2c20fd5e4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -70,6 +70,8 @@ cc_library( "include/nlohmann/detail/view/document_data.hpp", "include/nlohmann/detail/view/errors.hpp", "include/nlohmann/detail/view/input.hpp", + "include/nlohmann/detail/view/iterator.hpp", + "include/nlohmann/detail/view/lookup.hpp", "include/nlohmann/detail/view/macro_scope.hpp", "include/nlohmann/detail/view/macro_unscope.hpp", "include/nlohmann/detail/view/materialize.hpp", diff --git a/include/nlohmann/detail/view/iterator.hpp b/include/nlohmann/detail/view/iterator.hpp new file mode 100644 index 000000000..78f148609 --- /dev/null +++ b/include/nlohmann/detail/view/iterator.hpp @@ -0,0 +1,263 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // ptrdiff_t, size_t +#include // forward_iterator_tag +#include // string, to_string +#include // enable_if + +#include +#include +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// the result of view_iterator::operator->: keeps the view alive for the +/// duration of the member access +template +class arrow_proxy +{ + public: + explicit arrow_proxy(const View& v) noexcept + : m_view(v) + {} + + const View* operator->() const noexcept + { + return &m_view; + } + + private: + View m_view; +}; + +/*! +@brief forward iterator over the elements of a basic_json_view + +Iterates over the elements of an array or the member values of an object, in +document order; key() gives the key of an object member. As for basic_json, a +primitive value iterates as a range of one element (itself), and null as an +empty range. +*/ +template +class view_iterator +{ + public: + using iterator_category = std::forward_iterator_tag; + using value_type = View; + using difference_type = std::ptrdiff_t; + using pointer = arrow_proxy; + using reference = View; + using string_view_t = typename View::string_view_t; + + view_iterator() noexcept = default; + + /// @param[in] pos the element, or the key of the member + /// @param[in] object whether pos is a key (its value is the next node) + view_iterator(const document_data* d, const node* pos, bool object) noexcept + : m_doc(d), m_pos(pos), m_value_offset(object ? 1 : 0) + {} + + NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept + { + return View(m_doc, m_pos + m_value_offset); + } + + pointer operator->() const noexcept + { + return pointer(**this); + } + + NLOHMANN_VIEW_ALWAYS_INLINE view_iterator& operator++() noexcept + { + m_pos = document_data::after(m_pos + m_value_offset); + return *this; + } + + view_iterator operator++(int) noexcept + { + const view_iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos == b.m_pos; + } + + friend bool operator!=(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos != b.m_pos; + } + + /// the key of the current object member; throws invalid_iterator.207 for + /// other iterators, like basic_json's iterators + string_view_t key() const + { + if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) + { + throw_invalid_iterator(207, "cannot use key() for non-object iterators"); + } + return string_view_t(m_doc->str(*m_pos), m_pos->len); + } + + View value() const noexcept + { + return **this; + } + + /// whether the iterator runs over the members of an object + bool is_object_iterator() const noexcept + { + return m_value_offset != 0; + } + + private: + const document_data* m_doc = nullptr; + const node* m_pos = nullptr; + std::size_t m_value_offset = 0; ///< 1 for objects: the value follows its key +}; + +/*! +@brief a (key, value) item of basic_json_view::items() + +The key of an array element is its index, as for basic_json::items(). +Supports structured bindings: for (const auto [key, value] : view.items()) +*/ +template +class view_item +{ + public: + using string_view_t = typename View::string_view_t; + using iterator = view_iterator; + + view_item(const iterator& it, std::size_t index) + : m_it(it) + { + if (!it.is_object_iterator()) + { + m_index = std::to_string(index); + } + } + + /// the member key, or the element index for arrays + string_view_t key() const + { + if (m_it.is_object_iterator()) + { + return m_it.key(); + } + return string_view_t(m_index.data(), m_index.size()); + } + + View value() const noexcept + { + return *m_it; + } + + template::type = 0> + string_view_t get() const + { + return key(); + } + + template::type = 0> + View get() const noexcept + { + return value(); + } + + private: + iterator m_it; + std::string m_index{}; +}; + +/// the range returned by basic_json_view::items() +template +class view_items +{ + public: + using item = view_item; + + class iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = item; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = item; + + explicit iterator(const view_iterator& it) noexcept + : m_it(it) + {} + + item operator*() const + { + return item(m_it, m_index); + } + + iterator& operator++() noexcept + { + ++m_it; + ++m_index; + return *this; + } + + iterator operator++(int) noexcept + { + const iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const iterator& a, const iterator& b) noexcept + { + return a.m_it == b.m_it; + } + + friend bool operator!=(const iterator& a, const iterator& b) noexcept + { + return a.m_it != b.m_it; + } + + private: + view_iterator m_it; + std::size_t m_index = 0; + }; + + explicit view_items(const View& v) noexcept + : m_view(v) + {} + + iterator begin() const noexcept + { + return iterator(m_view.begin()); + } + + iterator end() const noexcept + { + return iterator(m_view.end()); + } + + private: + View m_view; +}; + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/view/lookup.hpp b/include/nlohmann/detail/view/lookup.hpp new file mode 100644 index 000000000..71338be32 --- /dev/null +++ b/include/nlohmann/detail/view/lookup.hpp @@ -0,0 +1,139 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // uint16_t, uint32_t, uint64_t +#include // memcmp, memcpy + +#include +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// equality test for strings of one length n <= 16: two overlapping loads per +/// string (the first and the last 8, 4, or 2 bytes) replace a memcmp, and no +/// byte outside [s, s + n) is read +class short_key +{ + public: + short_key(const unsigned char* k, std::size_t n) noexcept + : m_n(n) + { + load(k, m_a, m_b); + } + + NLOHMANN_VIEW_ALWAYS_INLINE bool matches(const unsigned char* s) const noexcept + { + std::uint64_t a = 0; + std::uint64_t b = 0; + load(s, a, b); + return a == m_a && b == m_b; + } + + private: + template + static NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t load_word(const unsigned char* s) noexcept + { + T w = 0; + std::memcpy(&w, s, sizeof(T)); + return w; + } + + NLOHMANN_VIEW_ALWAYS_INLINE void load(const unsigned char* s, std::uint64_t& a, std::uint64_t& b) const noexcept + { + if (m_n >= 8) + { + a = load_word(s); + b = load_word(s + m_n - 8); + } + else if (m_n >= 4) + { + a = load_word(s); + b = load_word(s + m_n - 4); + } + else if (m_n >= 2) + { + a = load_word(s); + b = load_word(s + m_n - 2); + } + else + { + a = m_n == 1 ? s[0] : 0; + b = 0; + } + } + + std::size_t m_n; + std::uint64_t m_a = 0; + std::uint64_t m_b = 0; +}; + +/// the key node of the first member of an object with the given key, or +/// nullptr; most keys are rejected by their length, from the index alone +inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept +{ + const node* const end = document_data::child_end(object); + const auto* const k = reinterpret_cast(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + if (NLOHMANN_VIEW_LIKELY(n <= 16)) + { + const short_key probe(k, n); + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && probe.matches(reinterpret_cast(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + { + return m; + } + } + return nullptr; + } + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) + { + return m; + } + } + return nullptr; +} + +/// the element of an array at an index below its size +inline const node* element_at(const node* array, std::size_t idx) noexcept +{ + const node* e = document_data::first_child(array); + for (std::size_t i = 0; i < idx; ++i) + { + e = document_data::after(e); + } + return e; +} + +/// the last element of a non-empty array, or the key of the last member of a +/// non-empty object +inline const node* last_child(const node* container) noexcept +{ + const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const node* const end = document_data::child_end(container); + const node* last = document_data::first_child(container); + for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) + { + last = c; + } + return last; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index e9f37d2de..63509e48d 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -29,6 +29,7 @@ #include // distance, input_iterator_tag, iterator_traits #include // unique_ptr #include // string +#include // tuple_element, tuple_size #include // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent #include // forward, move @@ -43,6 +44,8 @@ #include #include #include +#include +#include #include #include #include @@ -75,6 +78,11 @@ class basic_json_view using size_type = std::size_t; /// std::string_view from C++17 on using string_view_t = detail::view::string_ref; + /// forward iterator over elements (arrays) or member values (objects) + using iterator = detail::view::view_iterator; + using const_iterator = iterator; + /// a (key, value) item of items() + using item = detail::view::view_item; /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; @@ -162,6 +170,12 @@ class basic_json_view return m_node != nullptr; } + /// the name of the type, as basic_json::type_name() + const char* type_name() const noexcept + { + return detail::value_type_name(type()); + } + ////////////// // capacity // ////////////// @@ -211,6 +225,219 @@ class basic_json_view } } + //////////////////// + // element access // + //////////////////// + + /// the value of the member with this key (the first one, should the key + /// occur more than once); a discarded view if there is none. Throws + /// type_error.305 if this is not an object. + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); + } + return lookup(key); + } + + basic_json_view operator[](const char* key) const + { + return operator[](string_view_t(key)); + } + + basic_json_view operator[](const string_t& key) const + { + return operator[](string_view_t(key.data(), key.size())); + } + + /// the element at this index; a discarded view if the index is out of + /// range. Throws type_error.305 if this is not an array. + basic_json_view operator[](size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); + } + return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); + } + + /// (an int argument would be ambiguous between size_type and const char*) + basic_json_view operator[](int idx) const + { + return operator[](static_cast(idx)); + } + + /// the value of the member with this key (the first one, should the key + /// occur more than once). Throws type_error.304 if this is not an object, + /// and out_of_range.403 if there is no such member. + basic_json_view at(string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + const basic_json_view r = lookup(key); + if (NLOHMANN_VIEW_UNLIKELY(!r)) + { + detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); + } + return r; + } + + basic_json_view at(const char* key) const + { + return at(string_view_t(key)); + } + + basic_json_view at(const string_t& key) const + { + return at(string_view_t(key.data(), key.size())); + } + + /// the element at this index. Throws type_error.304 if this is not an + /// array, and out_of_range.401 if the index is out of range. + basic_json_view at(size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) + { + detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + } + return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); + } + + basic_json_view at(int idx) const + { + return at(static_cast(idx)); + } + + /// the first element or member value; a primitive value itself. Throws + /// invalid_iterator.214 for null, discarded views, and empty containers. + basic_json_view front() const + { + const iterator it = begin(); + if (NLOHMANN_VIEW_UNLIKELY(it == end())) + { + detail::view::throw_invalid_iterator(214, "cannot get value"); + } + return *it; + } + + /// the last element or member value (linear in the size); a primitive + /// value itself. Throws invalid_iterator.214 for null, discarded views, + /// and empty containers. + basic_json_view back() const + { + if (is_structured() && m_node->len != 0) + { + return basic_json_view(m_doc, detail::view::last_child(m_node) + (is_object() ? 1 : 0)); + } + return front(); + } + + //////////// + // lookup // + //////////// + + /// an iterator to the member with this key (the first one, should the + /// key occur more than once), or end(); end() also for non-objects + iterator find(string_view_t key) const + { + if (!is_object()) + { + return end(); + } + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? iterator(m_doc, k, true) : end(); + } + + iterator find(const char* key) const + { + return find(string_view_t(key)); + } + + iterator find(const string_t& key) const + { + return find(string_view_t(key.data(), key.size())); + } + + /// whether this is an object with a member with this key + bool contains(string_view_t key) const + { + return is_object() && detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr; + } + + bool contains(const char* key) const + { + return contains(string_view_t(key)); + } + + bool contains(const string_t& key) const + { + return contains(string_view_t(key.data(), key.size())); + } + + /// 1 if this is an object with a member with this key, else 0 (duplicate + /// keys count once) + size_type count(string_view_t key) const + { + return contains(key) ? 1 : 0; + } + + size_type count(const char* key) const + { + return count(string_view_t(key)); + } + + size_type count(const string_t& key) const + { + return count(string_view_t(key.data(), key.size())); + } + + /////////////// + // iteration // + /////////////// + + /// the first element or member value, in document order; a primitive + /// value is a range of one element (itself), null an empty range + NLOHMANN_VIEW_ALWAYS_INLINE iterator begin() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::first_child(m_node), is_object()); + } + return iterator(m_doc, m_node, false); + } + + NLOHMANN_VIEW_ALWAYS_INLINE iterator end() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::child_end(m_node), is_object()); + } + return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false); + } + + iterator cbegin() const noexcept + { + return begin(); + } + + iterator cend() const noexcept + { + return end(); + } + + /// (key, value) items; the key of an array element is its index + detail::view::view_items items() const noexcept + { + return detail::view::view_items(*this); + } + ///////////////// // materialize // ///////////////// @@ -237,11 +464,20 @@ class basic_json_view private: template friend class basic_json_document; + friend iterator; basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} + /// the value of the first member with this key, or a discarded view + /// (object required) + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept + { + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); + } + const document_data* m_doc = nullptr; const node* m_node = nullptr; }; @@ -587,6 +823,31 @@ using ordered_json_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END +// tuple protocol for the items of basic_json_view::items() (structured bindings) +namespace std // NOLINT(cert-dcl58-cpp) +{ + +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::view::view_item> // NOLINT(cert-dcl58-cpp) + : public std::integral_constant {}; + +template +class tuple_element> // NOLINT(cert-dcl58-cpp) +{ + public: + using type = decltype(std::declval<::nlohmann::detail::view::view_item>().template get()); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +} // namespace std + #include #endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_ diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index f97a01574..88f1ff015 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -29,6 +29,7 @@ #include // distance, input_iterator_tag, iterator_traits #include // unique_ptr #include // string +#include // tuple_element, tuple_size #include // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent #include // forward, move @@ -1742,6 +1743,419 @@ std::string collect_adapter(Adapter ia) } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // ptrdiff_t, size_t +#include // forward_iterator_tag +#include // string, to_string +#include // enable_if + +// #include +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// the result of view_iterator::operator->: keeps the view alive for the +/// duration of the member access +template +class arrow_proxy +{ + public: + explicit arrow_proxy(const View& v) noexcept + : m_view(v) + {} + + const View* operator->() const noexcept + { + return &m_view; + } + + private: + View m_view; +}; + +/*! +@brief forward iterator over the elements of a basic_json_view + +Iterates over the elements of an array or the member values of an object, in +document order; key() gives the key of an object member. As for basic_json, a +primitive value iterates as a range of one element (itself), and null as an +empty range. +*/ +template +class view_iterator +{ + public: + using iterator_category = std::forward_iterator_tag; + using value_type = View; + using difference_type = std::ptrdiff_t; + using pointer = arrow_proxy; + using reference = View; + using string_view_t = typename View::string_view_t; + + view_iterator() noexcept = default; + + /// @param[in] pos the element, or the key of the member + /// @param[in] object whether pos is a key (its value is the next node) + view_iterator(const document_data* d, const node* pos, bool object) noexcept + : m_doc(d), m_pos(pos), m_value_offset(object ? 1 : 0) + {} + + NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept + { + return View(m_doc, m_pos + m_value_offset); + } + + pointer operator->() const noexcept + { + return pointer(**this); + } + + NLOHMANN_VIEW_ALWAYS_INLINE view_iterator& operator++() noexcept + { + m_pos = document_data::after(m_pos + m_value_offset); + return *this; + } + + view_iterator operator++(int) noexcept + { + const view_iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos == b.m_pos; + } + + friend bool operator!=(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos != b.m_pos; + } + + /// the key of the current object member; throws invalid_iterator.207 for + /// other iterators, like basic_json's iterators + string_view_t key() const + { + if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) + { + throw_invalid_iterator(207, "cannot use key() for non-object iterators"); + } + return string_view_t(m_doc->str(*m_pos), m_pos->len); + } + + View value() const noexcept + { + return **this; + } + + /// whether the iterator runs over the members of an object + bool is_object_iterator() const noexcept + { + return m_value_offset != 0; + } + + private: + const document_data* m_doc = nullptr; + const node* m_pos = nullptr; + std::size_t m_value_offset = 0; ///< 1 for objects: the value follows its key +}; + +/*! +@brief a (key, value) item of basic_json_view::items() + +The key of an array element is its index, as for basic_json::items(). +Supports structured bindings: for (const auto [key, value] : view.items()) +*/ +template +class view_item +{ + public: + using string_view_t = typename View::string_view_t; + using iterator = view_iterator; + + view_item(const iterator& it, std::size_t index) + : m_it(it) + { + if (!it.is_object_iterator()) + { + m_index = std::to_string(index); + } + } + + /// the member key, or the element index for arrays + string_view_t key() const + { + if (m_it.is_object_iterator()) + { + return m_it.key(); + } + return string_view_t(m_index.data(), m_index.size()); + } + + View value() const noexcept + { + return *m_it; + } + + template::type = 0> + string_view_t get() const + { + return key(); + } + + template::type = 0> + View get() const noexcept + { + return value(); + } + + private: + iterator m_it; + std::string m_index{}; +}; + +/// the range returned by basic_json_view::items() +template +class view_items +{ + public: + using item = view_item; + + class iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = item; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = item; + + explicit iterator(const view_iterator& it) noexcept + : m_it(it) + {} + + item operator*() const + { + return item(m_it, m_index); + } + + iterator& operator++() noexcept + { + ++m_it; + ++m_index; + return *this; + } + + iterator operator++(int) noexcept + { + const iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const iterator& a, const iterator& b) noexcept + { + return a.m_it == b.m_it; + } + + friend bool operator!=(const iterator& a, const iterator& b) noexcept + { + return a.m_it != b.m_it; + } + + private: + view_iterator m_it; + std::size_t m_index = 0; + }; + + explicit view_items(const View& v) noexcept + : m_view(v) + {} + + iterator begin() const noexcept + { + return iterator(m_view.begin()); + } + + iterator end() const noexcept + { + return iterator(m_view.end()); + } + + private: + View m_view; +}; + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // uint16_t, uint32_t, uint64_t +#include // memcmp, memcpy + +// #include +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// equality test for strings of one length n <= 16: two overlapping loads per +/// string (the first and the last 8, 4, or 2 bytes) replace a memcmp, and no +/// byte outside [s, s + n) is read +class short_key +{ + public: + short_key(const unsigned char* k, std::size_t n) noexcept + : m_n(n) + { + load(k, m_a, m_b); + } + + NLOHMANN_VIEW_ALWAYS_INLINE bool matches(const unsigned char* s) const noexcept + { + std::uint64_t a = 0; + std::uint64_t b = 0; + load(s, a, b); + return a == m_a && b == m_b; + } + + private: + template + static NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t load_word(const unsigned char* s) noexcept + { + T w = 0; + std::memcpy(&w, s, sizeof(T)); + return w; + } + + NLOHMANN_VIEW_ALWAYS_INLINE void load(const unsigned char* s, std::uint64_t& a, std::uint64_t& b) const noexcept + { + if (m_n >= 8) + { + a = load_word(s); + b = load_word(s + m_n - 8); + } + else if (m_n >= 4) + { + a = load_word(s); + b = load_word(s + m_n - 4); + } + else if (m_n >= 2) + { + a = load_word(s); + b = load_word(s + m_n - 2); + } + else + { + a = m_n == 1 ? s[0] : 0; + b = 0; + } + } + + std::size_t m_n; + std::uint64_t m_a = 0; + std::uint64_t m_b = 0; +}; + +/// the key node of the first member of an object with the given key, or +/// nullptr; most keys are rejected by their length, from the index alone +inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept +{ + const node* const end = document_data::child_end(object); + const auto* const k = reinterpret_cast(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + if (NLOHMANN_VIEW_LIKELY(n <= 16)) + { + const short_key probe(k, n); + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && probe.matches(reinterpret_cast(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + { + return m; + } + } + return nullptr; + } + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) + { + return m; + } + } + return nullptr; +} + +/// the element of an array at an index below its size +inline const node* element_at(const node* array, std::size_t idx) noexcept +{ + const node* e = document_data::first_child(array); + for (std::size_t i = 0; i < idx; ++i) + { + e = document_data::after(e); + } + return e; +} + +/// the last element of a non-empty array, or the key of the last member of a +/// non-empty object +inline const node* last_child(const node* container) noexcept +{ + const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const node* const end = document_data::child_end(container); + const node* last = document_data::first_child(container); + for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) + { + last = c; + } + return last; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // #include @@ -2100,6 +2514,11 @@ class basic_json_view using size_type = std::size_t; /// std::string_view from C++17 on using string_view_t = detail::view::string_ref; + /// forward iterator over elements (arrays) or member values (objects) + using iterator = detail::view::view_iterator; + using const_iterator = iterator; + /// a (key, value) item of items() + using item = detail::view::view_item; /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; @@ -2187,6 +2606,12 @@ class basic_json_view return m_node != nullptr; } + /// the name of the type, as basic_json::type_name() + const char* type_name() const noexcept + { + return detail::value_type_name(type()); + } + ////////////// // capacity // ////////////// @@ -2236,6 +2661,219 @@ class basic_json_view } } + //////////////////// + // element access // + //////////////////// + + /// the value of the member with this key (the first one, should the key + /// occur more than once); a discarded view if there is none. Throws + /// type_error.305 if this is not an object. + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); + } + return lookup(key); + } + + basic_json_view operator[](const char* key) const + { + return operator[](string_view_t(key)); + } + + basic_json_view operator[](const string_t& key) const + { + return operator[](string_view_t(key.data(), key.size())); + } + + /// the element at this index; a discarded view if the index is out of + /// range. Throws type_error.305 if this is not an array. + basic_json_view operator[](size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); + } + return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); + } + + /// (an int argument would be ambiguous between size_type and const char*) + basic_json_view operator[](int idx) const + { + return operator[](static_cast(idx)); + } + + /// the value of the member with this key (the first one, should the key + /// occur more than once). Throws type_error.304 if this is not an object, + /// and out_of_range.403 if there is no such member. + basic_json_view at(string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + const basic_json_view r = lookup(key); + if (NLOHMANN_VIEW_UNLIKELY(!r)) + { + detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); + } + return r; + } + + basic_json_view at(const char* key) const + { + return at(string_view_t(key)); + } + + basic_json_view at(const string_t& key) const + { + return at(string_view_t(key.data(), key.size())); + } + + /// the element at this index. Throws type_error.304 if this is not an + /// array, and out_of_range.401 if the index is out of range. + basic_json_view at(size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) + { + detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + } + return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); + } + + basic_json_view at(int idx) const + { + return at(static_cast(idx)); + } + + /// the first element or member value; a primitive value itself. Throws + /// invalid_iterator.214 for null, discarded views, and empty containers. + basic_json_view front() const + { + const iterator it = begin(); + if (NLOHMANN_VIEW_UNLIKELY(it == end())) + { + detail::view::throw_invalid_iterator(214, "cannot get value"); + } + return *it; + } + + /// the last element or member value (linear in the size); a primitive + /// value itself. Throws invalid_iterator.214 for null, discarded views, + /// and empty containers. + basic_json_view back() const + { + if (is_structured() && m_node->len != 0) + { + return basic_json_view(m_doc, detail::view::last_child(m_node) + (is_object() ? 1 : 0)); + } + return front(); + } + + //////////// + // lookup // + //////////// + + /// an iterator to the member with this key (the first one, should the + /// key occur more than once), or end(); end() also for non-objects + iterator find(string_view_t key) const + { + if (!is_object()) + { + return end(); + } + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? iterator(m_doc, k, true) : end(); + } + + iterator find(const char* key) const + { + return find(string_view_t(key)); + } + + iterator find(const string_t& key) const + { + return find(string_view_t(key.data(), key.size())); + } + + /// whether this is an object with a member with this key + bool contains(string_view_t key) const + { + return is_object() && detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr; + } + + bool contains(const char* key) const + { + return contains(string_view_t(key)); + } + + bool contains(const string_t& key) const + { + return contains(string_view_t(key.data(), key.size())); + } + + /// 1 if this is an object with a member with this key, else 0 (duplicate + /// keys count once) + size_type count(string_view_t key) const + { + return contains(key) ? 1 : 0; + } + + size_type count(const char* key) const + { + return count(string_view_t(key)); + } + + size_type count(const string_t& key) const + { + return count(string_view_t(key.data(), key.size())); + } + + /////////////// + // iteration // + /////////////// + + /// the first element or member value, in document order; a primitive + /// value is a range of one element (itself), null an empty range + NLOHMANN_VIEW_ALWAYS_INLINE iterator begin() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::first_child(m_node), is_object()); + } + return iterator(m_doc, m_node, false); + } + + NLOHMANN_VIEW_ALWAYS_INLINE iterator end() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::child_end(m_node), is_object()); + } + return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false); + } + + iterator cbegin() const noexcept + { + return begin(); + } + + iterator cend() const noexcept + { + return end(); + } + + /// (key, value) items; the key of an array element is its index + detail::view::view_items items() const noexcept + { + return detail::view::view_items(*this); + } + ///////////////// // materialize // ///////////////// @@ -2262,11 +2900,20 @@ class basic_json_view private: template friend class basic_json_document; + friend iterator; basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} + /// the value of the first member with this key, or a discarded view + /// (object required) + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept + { + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); + } + const document_data* m_doc = nullptr; const node* m_node = nullptr; }; @@ -2612,6 +3259,31 @@ using ordered_json_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END +// tuple protocol for the items of basic_json_view::items() (structured bindings) +namespace std // NOLINT(cert-dcl58-cpp) +{ + +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::view::view_item> // NOLINT(cert-dcl58-cpp) + : public std::integral_constant {}; + +template +class tuple_element> // NOLINT(cert-dcl58-cpp) +{ + public: + using type = decltype(std::declval<::nlohmann::detail::view::view_item>().template get()); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +} // namespace std + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 4cfe9e40a..215e0e586 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -14,8 +14,11 @@ using nlohmann::ordered_json; using nlohmann::json_document; using nlohmann::json_view; using nlohmann::ordered_json_document; +using nlohmann::ordered_json_view; +#include #include +#include #include #include #include @@ -367,7 +370,282 @@ TEST_CASE("json_view") const std::string text = R"( {"key": "value", "escaped": "a\nb", "n": 42})"; const json_document d = json_document::parse(text); CHECK(d.root().source_offset() == 2); - // (element access comes with a later change; the offsets of the - // string nodes are checked through materialize() above) + CHECK(d.root()["key"].source_offset() == text.find("value")); + CHECK(d.root()["escaped"].source_offset() == static_cast(-1)); + CHECK(d.root()["n"].source_offset() == text.find("42")); + } +} + +namespace +{ +// the exception a call throws, or "" if it throws none +template +std::string exception_of(F f) +{ + try + { + f(); + } + catch (const json::exception& e) + { + return e.what(); + } + return ""; +} + +// compares a view with the ordered_json value materialize() gives for it: +// types, sizes, elements and members (by index, key, and iteration), in +// document order; duplicate keys are found as their first occurrence +void check_access(const ordered_json_view& v, const ordered_json& j) +{ + REQUIRE(v.type() == j.type()); + CHECK(std::string(v.type_name()) == j.type_name()); + if (v.is_array()) + { + REQUIRE(v.size() == j.size()); + std::size_t i = 0; + for (const ordered_json_view e : v) + { + CHECK(v[i].materialize() == e.materialize()); + CHECK(v.at(i).materialize() == e.materialize()); + check_access(e, j[i]); + ++i; + } + CHECK(i == v.size()); + CHECK(!v[v.size()]); + std::size_t index = 0; + for (const auto& item : v.items()) + { + CHECK(item.key() == std::to_string(index)); + CHECK(item.value().materialize() == j[index]); + ++index; + } + if (!v.empty()) + { + CHECK(v.front().materialize() == j.front()); + CHECK(v.back().materialize() == j.back()); + } + } + else if (v.is_object()) + { + std::vector keys; // first occurrences, in order + std::size_t members = 0; + for (auto it = v.begin(); it != v.end(); ++it) + { + ++members; + const std::string key(it.key().data(), it.key().size()); + CHECK(v.contains(key)); + CHECK(v.count(key) == 1); + if (std::find(keys.begin(), keys.end(), key) != keys.end()) + { + continue; // a duplicate: lookups find the first one + } + keys.push_back(key); + CHECK(v.find(key) == it); + CHECK(v[key].materialize() == it->materialize()); + CHECK(v.at(key).materialize() == it.value().materialize()); + CHECK(v[key.c_str()].materialize() == (*it).materialize()); + } + CHECK(members == v.size()); + REQUIRE(keys.size() == j.size()); + std::size_t k = 0; + for (const auto& member : j.items()) + { + CHECK(keys[k++] == member.key()); + } + if (keys.size() == members) + { + // no duplicates: the values are those of the object + for (const auto& key : keys) + { + check_access(v[key], j[key]); + } + if (!v.empty()) + { + CHECK(v.front().materialize() == j.front()); + CHECK(v.back().materialize() == j.back()); + } + } + CHECK(!v["not a key in the generated documents"]); + CHECK(v.find("not a key in the generated documents") == v.end()); + } + else + { + // a primitive is a range of one element; null is empty + CHECK(static_cast(std::distance(v.begin(), v.end())) == (v.is_null() ? 0u : 1u)); + if (!v.is_null()) + { + CHECK((*v.begin()).materialize() == j); + CHECK(v.front().materialize() == j); + CHECK(v.back().materialize() == j); + } + } +} +} // namespace + +TEST_CASE("json_view element access and iteration") +{ + SECTION("generated documents") + { + generator g; + for (int i = 0; i < 2000; ++i) + { + std::string text; + g.value(text, 0); + CAPTURE(text); + const ordered_json_document d = ordered_json_document::parse(text); + check_access(d.root(), ordered_json::parse(text)); + } + } + + SECTION("keys") + { + // keys of every length around the 2/4/8/16-byte loads, with escapes + std::string text = "{"; + std::vector keys = {"", "x"}; + for (std::size_t n = 1; n <= 40; ++n) + { + keys.push_back(std::string(n, 'k')); + keys.push_back(std::string(n, 'k') + "x"); + keys.push_back("x" + std::string(n, 'k')); + } + for (std::size_t i = 0; i < keys.size(); ++i) + { + text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i); + } + text += ",\"esc\\u0061ped\":\"escaped key\"}"; + const json_document d = json_document::parse(text); + const json_view root = d.root(); + for (std::size_t i = 0; i < keys.size(); ++i) + { + CAPTURE(keys[i]); + CHECK(root[keys[i]].materialize() == i); + CHECK(root.at(keys[i]).materialize() == i); + CHECK(root.find(keys[i]).key() == keys[i]); + CHECK(!root.contains(keys[i] + "y")); + } + CHECK(root["escaped"].materialize() == "escaped key"); + CHECK(!root.contains("esc\\u0061ped")); +#ifdef JSON_HAS_CPP_17 + CHECK(root[std::string_view("kkk")].materialize() == root["kkk"].materialize()); +#endif + } + + SECTION("duplicate keys: lookups find the first member, iteration all") + { + const json_document d = json_document::parse(R"({"a":1,"b":2,"a":3})"); + const json_view v = d.root(); + CHECK(v.size() == 3); + CHECK(v["a"].materialize() == 1); + CHECK(v.at("a").materialize() == 1); + CHECK(v.find("a") == v.begin()); + CHECK(v.count("a") == 1); + std::string order; + for (auto it = v.begin(); it != v.end(); ++it) + { + order += std::string(it.key().data(), it.key().size()) + it->materialize().dump(); + } + CHECK(order == "a1b2a3"); + CHECK(v.back().materialize() == 3); + CHECK(v.materialize() == json::parse(R"({"a":1,"b":2,"a":3})")); // the last value, as parse() + } + + SECTION("errors are those of const basic_json") + { + for (const char* text : + {"null", "true", "42", "-1", "1.5", "\"s\"", "[]", "[1,2]", "{}", "{\"a\":1}" + }) + { + CAPTURE(text); + const json_document d = json_document::parse(text); + const json_view v = d.root(); + const json j = v.materialize(); + if (!j.is_object()) + { + CHECK(exception_of([&] { static_cast(v["a"]); }) == exception_of([&] { static_cast(j["a"]); })); + } + if (!j.is_array()) + { + CHECK(exception_of([&] { static_cast(v[0]); }) == exception_of([&] { static_cast(j[0]); })); + } + CHECK(exception_of([&] { static_cast(v.at("a")); }) == exception_of([&] { static_cast(j.at("a")); })); + CHECK(exception_of([&] { static_cast(v.at("missing")); }) == exception_of([&] { static_cast(j.at("missing")); })); + CHECK(exception_of([&] { static_cast(v.at(0)); }) == exception_of([&] { static_cast(j.at(0)); })); + CHECK(exception_of([&] { static_cast(v.at(5)); }) == exception_of([&] { static_cast(j.at(5)); })); + if (!(j.is_object() && j.empty())) // (key() of an end iterator) + { + CHECK(exception_of([&] { static_cast(v.begin().key()); }) == exception_of([&] { static_cast(j.begin().key()); })); + } + if (!j.empty() || j.is_null()) + { + CHECK(exception_of([&] { static_cast(v.front()); }) == exception_of([&] { static_cast(j.front()); })); + CHECK(exception_of([&] { static_cast(v.back()); }) == exception_of([&] { static_cast(j.back()); })); + } + CHECK(v.contains("a") == j.contains("a")); + CHECK(v.count("a") == j.count("a")); + CHECK((v.find("a") == v.end()) == (j.find("a") == j.end())); + } + + // where basic_json has undefined behavior, the view answers safely + const json_document d = json_document::parse(R"({"a":[]})"); + CHECK(!d.root()["b"]); + CHECK(!d.root()["a"][0]); + CHECK_THROWS_WITH_AS(d.root()["a"].front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); + CHECK_THROWS_WITH_AS(d.root()["a"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); + const json_view invalid; + CHECK(invalid.begin() == invalid.end()); + CHECK(std::string(invalid.type_name()) == "discarded"); + CHECK_THROWS_WITH_AS(invalid["a"], "[json.exception.type_error.305] cannot use operator[] with a string argument with discarded", json::type_error&); + } + + SECTION("iterators") + { + const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})"); + const json_view v = d.root(); + json_view::iterator it = v.begin(); + CHECK(it.is_object_iterator()); + CHECK(it->is_array()); + CHECK(it->size() == 2); + const json_view::iterator previous = it++; + CHECK(previous.key() == "x"); + CHECK(it.key() == "z"); + CHECK(it.value().is_null()); + CHECK(++it == v.end()); + CHECK(v.cbegin() == v.begin()); + CHECK(v.cend() == v.end()); + CHECK(!v["x"].begin().is_object_iterator()); + CHECK(json_view::iterator() == json_view::iterator()); + // standard algorithms + CHECK(std::count_if(v["x"].begin(), v["x"].end(), [](const json_view & e) + { + return e.is_object(); + }) == 1); + } + + SECTION("items") + { + const json_document d = json_document::parse(R"({"a":1,"b":[true,false]})"); + std::string keys; + for (const auto& item : d.root().items()) + { + keys += std::string(item.key().data(), item.key().size()); + CHECK(item.value().materialize() == d.root()[item.key()].materialize()); + } + CHECK(keys == "ab"); + auto items = d.root()["b"].items(); + auto first = items.begin(); + CHECK((*first++).key() == "0"); + CHECK((*first).key() == "1"); + CHECK(++first == items.end()); +#ifdef JSON_HAS_CPP_17 + std::string pairs; + for (const auto [key, value] : d.root().items()) + { + pairs += std::string(key) + "=" + value.materialize().dump() + ";"; + } + CHECK(pairs == "a=1;b=[true,false];"); + static_assert(std::tuple_size::value == 2, ""); + static_assert(std::is_same::type, json_view>::value, ""); +#endif } }