Make view lookups last-wins and chained access safe

* Lookups (operator[], at, find, contains, count, value, JSON pointers)
  return the last member of a duplicate key, as materialize() and
  parse() keep it.
* operator[] on a discarded view returns a discarded view instead of
  throwing, so v["a"]["b"] is safe for a missing "a".
* operator[] and at() take any integer type (not only int and size_t),
  fixing ambiguous calls with unsigned, long, std::int64_t, ...
* Fix the operator[] documentation, which claimed a discarded view for
  a type mismatch where type_error.305 is thrown.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-11 10:27:59 +02:00
1 parent 041a561e56
commit 4ebac0263e
16 files changed
+306 -84

No files matched your search

+35 -15
View File
@@ -240,13 +240,18 @@ 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.
/// the value of the member with this key (the last one, should the key
/// occur more than once); a discarded view if there is none, or if this
/// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
/// if this is any other value but an object.
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
}
return lookup(key);
@@ -263,31 +268,43 @@ class basic_json_view
}
/// 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.
/// range, or if this is a discarded view. Throws type_error.305 if this is
/// any other value but an array.
basic_json_view operator[](size_type idx) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{
if (is_discarded())
{
return basic_json_view();
}
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
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
/// single overload for size_type alone would be ambiguous for all of them
/// and for const char*); negative values are out of range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view operator[](IntegerType idx) const
{
return operator[](static_cast<size_type>(idx));
return operator[](detail::view::to_index<size_type>(idx));
}
/// the value a JSON pointer refers to; a discarded view if a key is
/// missing or an index is out of range. Other errors throw what const
/// basic_json::operator[] throws.
/// missing or an index is out of range, or if this is a discarded view.
/// Other errors throw what const basic_json::operator[] throws.
basic_json_view operator[](const json_pointer& ptr) const
{
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
{
return basic_json_view();
}
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
}
/// the value of the member with this key (the first one, should the key
/// the value of the member with this key (the last 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
@@ -329,9 +346,12 @@ class basic_json_view
return basic_json_view(m_doc, detail::view::element_at(m_node, idx));
}
basic_json_view at(int idx) const
/// any other integer type, see operator[]; negative values are out of
/// range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view at(IntegerType idx) const
{
return at(static_cast<size_type>(idx));
return at(detail::view::to_index<size_type>(idx));
}
/// the value a JSON pointer refers to; throws what basic_json::at()
@@ -342,7 +362,7 @@ class basic_json_view
}
/// the member with this key converted to T, or the default value if there
/// is no such member (the first one, should the key occur more than
/// is no such member (the last one, should the key occur more than
/// once). Throws type_error.306 if this is not an object.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
T value(string_view_t key, const T& default_value) const
@@ -407,7 +427,7 @@ class basic_json_view
// lookup //
////////////
/// an iterator to the member with this key (the first one, should the
/// an iterator to the member with this key (the last one, should the
/// key occur more than once), or end(); end() also for non-objects
iterator find(string_view_t key) const
{
@@ -585,7 +605,7 @@ class basic_json_view
: m_doc(d), m_node(n)
{}
/// the value of the first member with this key, or a discarded view
/// the value of the last member with this key, or a discarded view
/// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{