Compare commits

..
Author SHA1 Message Date
Niels Lohmann b15ec08275 Merge branch 'develop' into claude/const-json-pointer-assertion
Conflicts:
- docs/mkdocs/docs/api/basic_json/operator[].md: version history item 1 keeps develop's std::length_error fix note and appends the PR's runtime assertion note

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:42:02 +02:00
Niels Lohmann 93fe62fc0b Assert on missing array indices in const operator[] and document the JSON pointer case
The const operator[] overloads are unchecked by design, and a missing key
or index is undefined behavior. The key overload guards this with a
runtime assertion, but the index overload did not, although the element
access documentation says an assertion fires in both cases. The const
JSON pointer overload inherits both through json_pointer::get_unchecked(),
so a pointer to a missing array index read out of bounds even in debug
builds, and its documentation promised out_of_range.404 for any pointer
that cannot be resolved.

Add JSON_ASSERT(idx < size()) to const operator[](size_type), which also
covers the index leg of the const JSON pointer overload. Document the
undefined behavior for the const JSON pointer overload in operator[].md
and in the runtime assertions page. Release builds are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 23:17:42 +02:00
13 changed files with 62 additions and 234 deletions
-1
View File
@@ -16,7 +16,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json', 'Class', 'api/ba
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::as_base_class', 'Method', 'api/basic_json/as_base_class/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html');
@@ -1,53 +0,0 @@
# <small>nlohmann::basic_json::</small>as_base_class
```cpp
json_base_class_t& as_base_class() noexcept;
const json_base_class_t& as_base_class() const noexcept;
```
Returns a reference to this object as its custom base class [`json_base_class_t`](json_base_class_t.md). No copy is
made.
Since `basic_json` derives from `json_base_class_t`, a member of `basic_json` hides any member of the custom base class
with the same name. This function makes such hidden members accessible again.
## Return value
reference to this object as [`json_base_class_t`](json_base_class_t.md)
## Exception safety
No-throw guarantee: this function never throws exceptions.
## Complexity
Constant.
## Notes
The function is equivalent to `static_cast<json_base_class_t&>(j)` (or `static_cast<const json_base_class_t&>(j)`).
## Examples
??? example
The example shows how to use `as_base_class` to access members of the custom base class that are hidden by members
of `basic_json`.
```cpp
--8<-- "examples/as_base_class.cpp"
```
Output:
```json
--8<-- "examples/as_base_class.output"
```
## See also
- [json_base_class_t](json_base_class_t.md) - type of the custom base class
## Version history
- Added in version 3.13.0.
-1
View File
@@ -200,7 +200,6 @@ Direct access to the stored value of a JSON value.
- [**get_ref**](get_ref.md) - get a reference value - [**get_ref**](get_ref.md) - get a reference value
- [**operator ValueType**](operator_ValueType.md) - get a value - [**operator ValueType**](operator_ValueType.md) - get a value
- [**get_binary**](get_binary.md) - get a binary value - [**get_binary**](get_binary.md) - get a binary value
- [**as_base_class**](as_base_class.md) - access the custom base class
### Element access ### Element access
@@ -27,18 +27,6 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s
[standard layout](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType) guarantee. See [standard layout](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType) guarantee. See
[Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass). [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass).
#### Name conflicts
Since `basic_json` derives from `CustomBaseClass`, members of `basic_json` hide members of `CustomBaseClass` with the
same name. Hidden members remain accessible via [`as_base_class`](as_base_class.md) or by casting the value to
`json_base_class_t`.
!!! warning "Avoid generic member names"
Future versions of the library may add members to `basic_json` that hide members of `CustomBaseClass` that are
accessible today. To reduce the risk of such conflicts, avoid generic names for the members of `CustomBaseClass`,
for instance by using a distinctive prefix.
## Examples ## Examples
??? example ??? example
@@ -55,11 +43,6 @@ same name. Hidden members remain accessible via [`as_base_class`](as_base_class.
--8<-- "examples/json_base_class_t.output" --8<-- "examples/json_base_class_t.output"
``` ```
## See also
- [as_base_class](as_base_class.md) - access the custom base class
## Version history ## Version history
- Added in version 3.12.0. - Added in version 3.12.0.
- Made a public member type in version 3.13.0; it was private before, so it could not be named outside the class.
+11 -3
View File
@@ -89,6 +89,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
- Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed - Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed
JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms). JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms).
For the **const** version, an object key or array index in `ptr` that does not exist is not reported by an
exception, but is undefined behavior (see the notes below). Use [`at`](at.md) for checked access.
## Complexity ## Complexity
1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`. 1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`.
@@ -103,9 +106,12 @@ Strong exception safety: if an exception occurs, the original value stays intact
The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element
(see the notes below). (see the notes below).
1. If the element at index `idx` does not exist, the behavior is undefined. 1. If the element at index `idx` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**!
2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a 2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**! [runtime assertion](../../features/assertions.md)**!
3. If the JSON pointer `ptr` refers to an object key or an array index that does not exist, the behavior is
undefined and is **guarded by a [runtime assertion](../../features/assertions.md)**!
1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the 1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the
array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In
@@ -261,9 +267,11 @@ Strong exception safety: if an exception occurs, the original value stays intact
## Version history ## Version history
1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and 1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and
accessing it out of bounds when `idx` equals the maximum value of `size_type`. accessing it out of bounds when `idx` equals the maximum value of `size_type`. A missing index in the const version
is guarded by a runtime assertion since version 3.13.0.
2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3) 2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3)
in version 3.11.0. in version 3.11.0.
3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as
already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions. already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions.
4. Added in version 2.0.0. 4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since
version 3.13.0.
@@ -1,41 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
class base_class_with_hidden_members
{
public:
const char* type_name() const noexcept
{
return "my_type_name";
}
std::size_t size() const noexcept
{
return 42;
}
};
using json = nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
base_class_with_hidden_members
>;
int main()
{
json j = {1, 2, 3};
// the members of basic_json hide the members of the base class
std::cout << j.type_name() << ' ' << j.size() << '\n';
// access the hidden members of the base class
std::cout << j.as_base_class().type_name() << ' ' << j.as_base_class().size() << '\n';
}
@@ -1,2 +0,0 @@
array 3
my_type_name 42
+31 -7
View File
@@ -16,14 +16,15 @@ before including the `json.hpp` header.
## Function with runtime assertions ## Function with runtime assertions
### Unchecked object access to a const value ### Unchecked access to a const value
Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for objects. Whereas a missing Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for arrays and objects. Whereas
key is added in the case of non-const objects, accessing a const object with a missing key is undefined behavior (think a missing element is added in the case of non-const values, accessing a const value with a missing object key or an
of a dereferenced null pointer) and yields a runtime assertion. invalid array index is undefined behavior (think of a dereferenced null pointer) and yields a runtime assertion. This
also applies to a [JSON pointer](json_pointer.md) that refers to a missing key or an invalid index.
If you are not sure whether an element in an object exists, use checked access with the If you are not sure whether an element exists, use checked access with the [`at` function](../api/basic_json/at.md)
[`at` function](../api/basic_json/at.md) or call the [`contains` function](../api/basic_json/contains.md) before. or call the [`contains` function](../api/basic_json/contains.md) before.
See also the documentation on [element access](element_access/index.md). See also the documentation on [element access](element_access/index.md).
@@ -46,7 +47,30 @@ See also the documentation on [element access](element_access/index.md).
Output: Output:
``` ```
Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144. Assertion failed: (it != m_data.m_value.object->end()), function operator[], file json.hpp, line 28795.
```
??? example "Example 2: Invalid array index in a JSON pointer"
The following code will trigger an assertion at runtime:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace nlohmann::literals;
int main()
{
const json j = {{"array", {1, 2, 3}}};
auto v = j["/array/5"_json_pointer];
}
```
Output:
```
Assertion failed: (idx < m_data.m_value.array->size()), function operator[], file json.hpp, line 28758.
``` ```
### Constructing from an uninitialized iterator range ### Constructing from an uninitialized iterator range
-1
View File
@@ -114,7 +114,6 @@ nav:
- 'accept': api/basic_json/accept.md - 'accept': api/basic_json/accept.md
- 'array': api/basic_json/array.md - 'array': api/basic_json/array.md
- 'array_t': api/basic_json/array_t.md - 'array_t': api/basic_json/array_t.md
- 'as_base_class': api/basic_json/as_base_class.md
- 'at': api/basic_json/at.md - 'at': api/basic_json/at.md
- 'back': api/basic_json/back.md - 'back': api/basic_json/back.md
- 'begin': api/basic_json/begin.md - 'begin': api/basic_json/begin.md
+8 -2
View File
@@ -536,6 +536,10 @@ class json_pointer
@return const reference to the JSON value pointed to by the JSON @return const reference to the JSON value pointed to by the JSON
pointer pointer
@pre Every object key and array index the pointer refers to exists.
Like the const operator[] for keys and indices, a missing one is
undefined behavior, guarded by a runtime assertion.
@throw parse_error.106 if an array index begins with '0' @throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number @throw parse_error.109 if an array index was not a number
@throw out_of_range.402 if the array index '-' is used @throw out_of_range.402 if the array index '-' is used
@@ -550,7 +554,8 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
// use unchecked object access // use unchecked object access; the const operator[]
// asserts that the key exists
ptr = &ptr->operator[](reference_token); ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -563,7 +568,8 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
} }
// use unchecked array access // use unchecked array access; the const operator[]
// asserts that the index exists
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
break; break;
} }
+2 -17
View File
@@ -155,6 +155,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// workaround type for MSVC /// workaround type for MSVC
using basic_json_t = NLOHMANN_BASIC_JSON_TPL; using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
// convenience aliases for types residing in namespace detail; // convenience aliases for types residing in namespace detail;
@@ -214,9 +215,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using cbor_tag_handler_t = detail::cbor_tag_handler_t; using cbor_tag_handler_t = detail::cbor_tag_handler_t;
/// how to encode BJData /// how to encode BJData
using bjdata_version_t = detail::bjdata_version_t; using bjdata_version_t = detail::bjdata_version_t;
/// base class used to inject custom functionality into each instance of basic_json
/// @sa https://json.nlohmann.me/api/basic_json/json_base_class_t/
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
/// helper type for initializer lists of basic_json values /// helper type for initializer lists of basic_json values
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
@@ -2693,20 +2691,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *get_ptr<const binary_t*>(); return *get_ptr<const binary_t*>();
} }
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
json_base_class_t& as_base_class() noexcept
{
return static_cast<json_base_class_t&>(*this);
}
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
const json_base_class_t& as_base_class() const noexcept
{
return static_cast<const json_base_class_t&>(*this);
}
/// @} /// @}
//////////////////// ////////////////////
@@ -2890,6 +2874,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for arrays // const operator[] only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
JSON_ASSERT(idx < m_data.m_value.array->size());
return m_data.m_value.array->operator[](idx); return m_data.m_value.array->operator[](idx);
} }
+10 -19
View File
@@ -20148,6 +20148,10 @@ class json_pointer
@return const reference to the JSON value pointed to by the JSON @return const reference to the JSON value pointed to by the JSON
pointer pointer
@pre Every object key and array index the pointer refers to exists.
Like the const operator[] for keys and indices, a missing one is
undefined behavior, guarded by a runtime assertion.
@throw parse_error.106 if an array index begins with '0' @throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number @throw parse_error.109 if an array index was not a number
@throw out_of_range.402 if the array index '-' is used @throw out_of_range.402 if the array index '-' is used
@@ -20162,7 +20166,8 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
// use unchecked object access // use unchecked object access; the const operator[]
// asserts that the key exists
ptr = &ptr->operator[](reference_token); ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -20175,7 +20180,8 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
} }
// use unchecked array access // use unchecked array access; the const operator[]
// asserts that the index exists
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
break; break;
} }
@@ -27082,6 +27088,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// workaround type for MSVC /// workaround type for MSVC
using basic_json_t = NLOHMANN_BASIC_JSON_TPL; using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
// convenience aliases for types residing in namespace detail; // convenience aliases for types residing in namespace detail;
@@ -27141,9 +27148,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using cbor_tag_handler_t = detail::cbor_tag_handler_t; using cbor_tag_handler_t = detail::cbor_tag_handler_t;
/// how to encode BJData /// how to encode BJData
using bjdata_version_t = detail::bjdata_version_t; using bjdata_version_t = detail::bjdata_version_t;
/// base class used to inject custom functionality into each instance of basic_json
/// @sa https://json.nlohmann.me/api/basic_json/json_base_class_t/
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
/// helper type for initializer lists of basic_json values /// helper type for initializer lists of basic_json values
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
@@ -29620,20 +29624,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *get_ptr<const binary_t*>(); return *get_ptr<const binary_t*>();
} }
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
json_base_class_t& as_base_class() noexcept
{
return static_cast<json_base_class_t&>(*this);
}
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
const json_base_class_t& as_base_class() const noexcept
{
return static_cast<const json_base_class_t&>(*this);
}
/// @} /// @}
//////////////////// ////////////////////
@@ -29817,6 +29807,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for arrays // const operator[] only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
JSON_ASSERT(idx < m_data.m_value.array->size());
return m_data.m_value.array->operator[](idx); return m_data.m_value.array->operator[](idx);
} }
-70
View File
@@ -10,8 +10,6 @@
#include <set> #include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits>
#include <utility>
#include <vector> #include <vector>
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -407,71 +405,3 @@ TEST_CASE("JSON Visit Node")
); );
CHECK(expected.empty()); CHECK(expected.empty());
} }
// Test accessing members of a custom base class that are hidden by members of nlohmann::basic_json
class base_class_with_hidden_members
{
public:
const char* type_name() const noexcept // NOLINT(readability-convert-member-functions-to-static)
{
return "custom type_name";
}
std::size_t size() const noexcept
{
return m_size;
}
std::size_t m_size = 42;
};
using json_with_hidden_base_members =
nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
base_class_with_hidden_members
>;
TEST_CASE("JSON Node as_base_class")
{
using json = json_with_hidden_base_members;
static_assert(std::is_same<decltype(std::declval<json&>().as_base_class()), json::json_base_class_t&>::value, "");
static_assert(std::is_same<decltype(std::declval<const json&>().as_base_class()), const json::json_base_class_t&>::value, "");
static_assert(noexcept(std::declval<json&>().as_base_class()), "");
static_assert(noexcept(std::declval<const json&>().as_base_class()), "");
SECTION("non-const")
{
json j = {1, 2, 3};
CHECK(std::string(j.type_name()) == "array");
CHECK(j.size() == 3);
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
CHECK(j.as_base_class().size() == 42);
CHECK(&j.as_base_class() == &static_cast<json::json_base_class_t&>(j));
j.as_base_class().m_size = 7;
CHECK(j.as_base_class().size() == 7);
CHECK(j.size() == 3);
}
SECTION("const")
{
const json j = {1, 2, 3};
CHECK(std::string(j.type_name()) == "array");
CHECK(j.size() == 3);
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
CHECK(j.as_base_class().size() == 42);
CHECK(&j.as_base_class() == &static_cast<const json::json_base_class_t&>(j));
}
}