Add element access, lookup, and iteration to json_view

basic_json_view gains the read-only access functions of basic_json:
operator[] and at() with keys and indices, front(), back(), find(),
contains(), count(), begin()/end(), items() (with structured bindings from
C++17 on), and type_name(). They throw the exceptions (ids and messages)
that the const functions of basic_json throw; where basic_json has
undefined behavior (operator[] with a missing key or an index out of
range, front()/back() of an empty container), the view returns a
discarded view or throws invalid_iterator.214.

Objects are iterated in document order, and all members are visited. With
duplicate keys, lookups find the first member, so that a lookup can stop
at the first match; parse() keeps the last value. Keys of up to 16 bytes
are compared with two overlapping loads instead of memcmp, and most keys
are rejected by their length alone, from the index.

The iterators and items live in detail/view/iterator.hpp, the lookups in
detail/view/lookup.hpp. Tests compare every element and member of 2,000
generated documents with ordered_json, keys of every length around the
load sizes, the exceptions against const basic_json, and the iterators.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-29 14:02:49 +02:00
parent 857953b39a
commit f80a1a17a9
6 changed files with 1617 additions and 2 deletions
+2
View File
@@ -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",
+263
View File
@@ -0,0 +1,263 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef> // ptrdiff_t, size_t
#include <iterator> // forward_iterator_tag
#include <string> // string, to_string
#include <type_traits> // enable_if
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp>
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<typename View>
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<typename View>
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<View>;
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<typename View>
class view_item
{
public:
using string_view_t = typename View::string_view_t;
using iterator = view_iterator<View>;
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<std::size_t N, typename std::enable_if<N == 0, int>::type = 0>
string_view_t get() const
{
return key();
}
template<std::size_t N, typename std::enable_if<N == 1, int>::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<typename View>
class view_items
{
public:
using item = view_item<View>;
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<View>& 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<View> 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
+139
View File
@@ -0,0 +1,139 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp>
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<typename T>
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<std::uint64_t>(s);
b = load_word<std::uint64_t>(s + m_n - 8);
}
else if (m_n >= 4)
{
a = load_word<std::uint32_t>(s);
b = load_word<std::uint32_t>(s + m_n - 4);
}
else if (m_n >= 2)
{
a = load_word<std::uint16_t>(s);
b = load_word<std::uint16_t>(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<const unsigned char*>(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<const unsigned char*>(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<std::uint8_t>(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
+261
View File
@@ -29,6 +29,7 @@
#include <iterator> // distance, input_iterator_tag, iterator_traits
#include <memory> // unique_ptr
#include <string> // string
#include <tuple> // tuple_element, tuple_size
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
#include <utility> // forward, move
@@ -43,6 +44,8 @@
#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/materialize.hpp>
#include <nlohmann/detail/view/node.hpp>
@@ -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<basic_json_view>;
using const_iterator = iterator;
/// a (key, value) item of items()
using item = detail::view::view_item<basic_json_view>;
/// 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<size_type>(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<size_type>(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<basic_json_view> items() const noexcept
{
return detail::view::view_items<basic_json_view>(*this);
}
/////////////////
// materialize //
/////////////////
@@ -237,11 +464,20 @@ class basic_json_view
private:
template<typename> 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<ordered_json>;
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<typename View>
class tuple_size<::nlohmann::detail::view::view_item<View>> // NOLINT(cert-dcl58-cpp)
: public std::integral_constant<std::size_t, 2> {};
template<std::size_t N, typename View>
class tuple_element<N, ::nlohmann::detail::view::view_item<View>> // NOLINT(cert-dcl58-cpp)
{
public:
using type = decltype(std::declval<::nlohmann::detail::view::view_item<View>>().template get<N>());
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
} // namespace std
#include <nlohmann/detail/view/macro_unscope.hpp>
#endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_
+672
View File
@@ -29,6 +29,7 @@
#include <iterator> // distance, input_iterator_tag, iterator_traits
#include <memory> // unique_ptr
#include <string> // string
#include <tuple> // tuple_element, tuple_size
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
#include <utility> // forward, move
@@ -1742,6 +1743,419 @@ std::string collect_adapter(Adapter ia)
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/iterator.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include <cstddef> // ptrdiff_t, size_t
#include <iterator> // forward_iterator_tag
#include <string> // string, to_string
#include <type_traits> // enable_if
// #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/document_data.hpp>
// #include <nlohmann/detail/view/errors.hpp>
// #include <nlohmann/detail/view/macro_scope.hpp>
// #include <nlohmann/detail/view/node.hpp>
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<typename View>
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<typename View>
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<View>;
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<typename View>
class view_item
{
public:
using string_view_t = typename View::string_view_t;
using iterator = view_iterator<View>;
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<std::size_t N, typename std::enable_if<N == 0, int>::type = 0>
string_view_t get() const
{
return key();
}
template<std::size_t N, typename std::enable_if<N == 1, int>::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<typename View>
class view_items
{
public:
using item = view_item<View>;
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<View>& 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<View> 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 <nlohmann/detail/view/lookup.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy
// #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/document_data.hpp>
// #include <nlohmann/detail/view/macro_scope.hpp>
// #include <nlohmann/detail/view/node.hpp>
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<typename T>
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<std::uint64_t>(s);
b = load_word<std::uint64_t>(s + m_n - 8);
}
else if (m_n >= 4)
{
a = load_word<std::uint32_t>(s);
b = load_word<std::uint32_t>(s + m_n - 4);
}
else if (m_n >= 2)
{
a = load_word<std::uint16_t>(s);
b = load_word<std::uint16_t>(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<const unsigned char*>(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<const unsigned char*>(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<std::uint8_t>(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 <nlohmann/detail/view/macro_scope.hpp>
// #include <nlohmann/detail/view/materialize.hpp>
@@ -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<basic_json_view>;
using const_iterator = iterator;
/// a (key, value) item of items()
using item = detail::view::view_item<basic_json_view>;
/// 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<size_type>(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<size_type>(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<basic_json_view> items() const noexcept
{
return detail::view::view_items<basic_json_view>(*this);
}
/////////////////
// materialize //
/////////////////
@@ -2262,11 +2900,20 @@ class basic_json_view
private:
template<typename> 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<ordered_json>;
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<typename View>
class tuple_size<::nlohmann::detail::view::view_item<View>> // NOLINT(cert-dcl58-cpp)
: public std::integral_constant<std::size_t, 2> {};
template<std::size_t N, typename View>
class tuple_element<N, ::nlohmann::detail::view::view_item<View>> // NOLINT(cert-dcl58-cpp)
{
public:
using type = decltype(std::declval<::nlohmann::detail::view::view_item<View>>().template get<N>());
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
} // namespace std
// #include <nlohmann/detail/view/macro_unscope.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
+280 -2
View File
@@ -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 <algorithm>
#include <cstdint>
#include <iterator>
#include <list>
#include <random>
#include <sstream>
@@ -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<std::size_t>(-1));
CHECK(d.root()["n"].source_offset() == text.find("42"));
}
}
namespace
{
// the exception a call throws, or "" if it throws none
template<typename F>
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<std::string> 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::size_t>(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<std::string> 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<void>(v["a"]); }) == exception_of([&] { static_cast<void>(j["a"]); }));
}
if (!j.is_array())
{
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == exception_of([&] { static_cast<void>(j[0]); }));
}
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == exception_of([&] { static_cast<void>(j.at("a")); }));
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == exception_of([&] { static_cast<void>(j.at("missing")); }));
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == exception_of([&] { static_cast<void>(j.at(0)); }));
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == exception_of([&] { static_cast<void>(j.at(5)); }));
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
{
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == exception_of([&] { static_cast<void>(j.begin().key()); }));
}
if (!j.empty() || j.is_null())
{
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == exception_of([&] { static_cast<void>(j.front()); }));
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == exception_of([&] { static_cast<void>(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<json_view::item>::value == 2, "");
static_assert(std::is_same<std::tuple_element<1, json_view::item>::type, json_view>::value, "");
#endif
}
}