diff --git a/docs/mkdocs/docs/api/eval.md b/docs/mkdocs/docs/api/eval.md
new file mode 100644
index 0000000000..bf19bbbbf9
--- /dev/null
+++ b/docs/mkdocs/docs/api/eval.md
@@ -0,0 +1,238 @@
+# nlohmann::eval_value, eval_array, eval_object
+
+```cpp
+#include
+
+namespace nlohmann
+{
+
+// (1) -- access a JSON object element by key with a default fallback
+template
+ValueType eval_value(const BasicJsonType& j,
+ const typename BasicJsonType::object_t::key_type& key,
+ const ValueType& default_value) noexcept;
+
+// (2) -- access a JSON value by JSON Pointer with a default fallback
+template
+ValueType eval_value(const BasicJsonType& j,
+ const typename BasicJsonType::json_pointer& ptr,
+ const ValueType& default_value) noexcept;
+
+// (3) -- access a JSON array stored at @a key
+template
+const BasicJsonType& eval_array(const BasicJsonType& j,
+ const typename BasicJsonType::object_t::key_type& key) noexcept;
+
+// (4) -- access a JSON array resolved by @a ptr
+template
+const BasicJsonType& eval_array(const BasicJsonType& j,
+ const typename BasicJsonType::json_pointer& ptr) noexcept;
+
+// (5) -- access a JSON object stored at @a key
+template
+const BasicJsonType& eval_object(const BasicJsonType& j,
+ const typename BasicJsonType::object_t::key_type& key) noexcept;
+
+// (6) -- access a JSON object resolved by @a ptr
+template
+const BasicJsonType& eval_object(const BasicJsonType& j,
+ const typename BasicJsonType::json_pointer& ptr) noexcept;
+
+}
+```
+
+Null-safe, `noexcept` accessors for retrieving values from a JSON object,
+where "null" refers to the JSON value [`null`](../features/types/index.md)
+(or any non-matching JSON shape) rather than to a null C++ object.
+
+These free functions are an opt-in alternative to
+[`basic_json::value`](basic_json/value.md). Unlike `value()`, they **never
+throw** -- on any non-matching condition (the receiver is not a JSON
+object, the key/pointer is missing, the resolved JSON value is `null`, or
+has a type that is not convertible to `ValueType`) they silently fall back
+to the supplied default value (overloads 1-2) or to a static empty JSON
+array/object (overloads 3-6).
+
+This brings the developer experience of accessing untrusted server-side JSON
+closer to JavaScript's optional chaining (`?.`) and nullish coalescing (`??`).
+
+1. Returns the value stored at @a key converted to `ValueType`. Returns
+ @a default_value if @a j is not a JSON object, @a key does not exist
+ in @a j, the value at @a key is JSON `null`, or the conversion fails.
+2. As (1), but the position is identified by a
+ [JSON Pointer](json_pointer/index.md). Any failure to resolve @a ptr
+ (missing intermediate node, wrong type along the way, etc.) yields
+ @a default_value.
+3. Returns a const reference to the JSON array stored at @a key. Returns
+ a reference to a static empty array if @a j is not a JSON object,
+ @a key does not exist, or the value at @a key is not a JSON array.
+4. As (3), but the position is identified by a
+ [JSON Pointer](json_pointer/index.md).
+5. Returns a const reference to the JSON object stored at @a key. Returns
+ a reference to a static empty object if @a j is not a JSON object,
+ @a key does not exist, or the value at @a key is not a JSON object.
+6. As (5), but the position is identified by a
+ [JSON Pointer](json_pointer/index.md).
+
+## Template parameters
+
+`BasicJsonType`
+: a specialization of [`basic_json`](basic_json/index.md).
+
+`ValueType`
+: a type compatible with JSON values. For overloads (1) and (2), it is
+ deduced from @a default_value, so the common case never requires
+ explicit template arguments.
+
+## Parameters
+
+`j` (in)
+: the JSON value to query.
+
+`key` (in)
+: the object key to look up (overloads (1), (3), (5)).
+
+`ptr` (in)
+: a [JSON Pointer](json_pointer/index.md) identifying the position to
+ look up (overloads (2), (4), (6)).
+
+`default_value` (in)
+: the value to return when no matching value can be retrieved
+ (overloads (1) and (2)).
+
+## Return value
+
+1. The value stored at @a key converted to `ValueType`, or @a default_value
+ on any non-matching condition.
+2. The JSON value resolved by @a ptr converted to `ValueType`, or
+ @a default_value on any non-matching condition.
+3. A const reference to the JSON array stored at @a key, or a const
+ reference to a static empty array on any non-matching condition.
+4. A const reference to the JSON array resolved by @a ptr, or a const
+ reference to a static empty array on any non-matching condition.
+5. A const reference to the JSON object stored at @a key, or a const
+ reference to a static empty object on any non-matching condition.
+6. A const reference to the JSON object resolved by @a ptr, or a const
+ reference to a static empty object on any non-matching condition.
+
+## Exception safety
+
+All overloads are declared `noexcept` and never throw.
+
+## Complexity
+
+1. Logarithmic in the size of @a j (one object lookup).
+2. Linear in the length of @a ptr.
+3. Logarithmic in the size of @a j.
+4. Linear in the length of @a ptr.
+5. Logarithmic in the size of @a j.
+6. Linear in the length of @a ptr.
+
+## Notes
+
+!!! note "Comparison with `value()`"
+
+ | Condition | `j.value(...)` | `eval_value(j, ...)` |
+ | -------------------------------------------- | ---------------------------- | -------------------- |
+ | `j` is object, key exists, correct type | returns value | returns value |
+ | `j` is object, key missing | returns default | returns default |
+ | `j` is JSON `null` | **throws `type_error`** | returns default |
+ | `j` is array, string, number, bool, ... | **throws `type_error`** | returns default |
+ | resolved value is JSON `null` | returns null-converted value | returns default |
+
+!!! note "Implementation strategy"
+
+ These helpers rely only on the public API of
+ [`basic_json`](basic_json/index.md): `is_object`, `is_array`,
+ `is_null`, `find`, `end`, `get`, `contains(json_pointer)`, and
+ `at(json_pointer)`. They are intentionally provided as non-member
+ functions in an opt-in header (``). They are not
+ pulled in by `` and are not bundled into the
+ amalgamated `single_include/nlohmann/json.hpp`.
+
+ The empty fallback array/object returned by reference is a
+ process-lifetime singleton constructed once into properly-aligned
+ uninitialized storage via placement-new. Its destructor is
+ intentionally never invoked at process exit, which avoids both
+ Clang's `-Wexit-time-destructors` and any
+ static-destruction-order concerns.
+
+!!! warning "Limitation under `JSON_NOEXCEPTION`"
+
+ The `noexcept` guarantee is best-effort under
+ [`JSON_NOEXCEPTION`](macros/json_noexception.md):
+
+ - `eval_array` and `eval_object` (both key and JSON Pointer overloads)
+ and the JSON Pointer overload of `eval_value` remain fully
+ noexcept-correct, because they only rely on `is_*` predicates,
+ `find`, `contains`, and `at` paths that are guarded by a successful
+ `contains` check.
+ - `eval_value(j, key, default)` calls `it->get()` for type
+ conversion. Under `JSON_NOEXCEPTION`, a conversion failure inside
+ `from_json` calls `std::abort()` instead of throwing, so passing a
+ receiver where the value at @a key is not convertible to `ValueType`
+ may abort the process. Use the JSON Pointer overload, or perform an
+ explicit `is_*` check at the call site, when running with exceptions
+ disabled.
+
+## Examples
+
+??? example "Example: safe access on a possibly-null payload"
+
+ The example below demonstrates safe defensive access to a JSON value
+ received from an untrusted source.
+
+ ```cpp
+ #include
+ #include
+
+ using nlohmann::json;
+
+ // a payload received from a remote source -- could be JSON null,
+ // partial, of the wrong type, or fully populated
+ json received = nullptr;
+
+ int a = nlohmann::eval_value(received, "a", 0);
+ auto d = nlohmann::eval_value(received,
+ json::json_pointer("/c/d"),
+ std::string{});
+
+ // safe range-based for: returns an empty array when "items" is missing
+ // or has the wrong shape
+ for (const auto& item : nlohmann::eval_array(received, "items"))
+ {
+ // process each item
+ (void) item;
+ }
+
+ // safe iteration over a nested object
+ for (const auto& kv : nlohmann::eval_object(received, "metadata").items())
+ {
+ // process each key/value pair
+ (void) kv;
+ }
+ ```
+
+??? example "Example: ADL allows omitting the namespace qualifier"
+
+ Because the helpers live in `namespace nlohmann`, they are found by
+ argument-dependent lookup, so the explicit qualifier is optional in
+ user code:
+
+ ```cpp
+ const json j = {{"a", 7}};
+ auto a = eval_value(j, "a", 0); // ADL finds nlohmann::eval_value
+ ```
+
+## See also
+
+- [`basic_json::value`](basic_json/value.md) -- exception-throwing
+ counterpart with different semantics on non-object receivers.
+- [JSON Pointer](json_pointer/index.md) -- the address syntax accepted
+ by the pointer overloads.
+- Discussion [#5129](https://github.com/nlohmann/json/discussions/5129) --
+ motivation and design rationale.
+
+## Version history
+
+- Added in version 3.12.1.
diff --git a/include/nlohmann/eval.hpp b/include/nlohmann/eval.hpp
new file mode 100644
index 0000000000..e05d16f245
--- /dev/null
+++ b/include/nlohmann/eval.hpp
@@ -0,0 +1,330 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 // array, used for aligned storage of the empty singletons
+#include // placement new
+
+#include
+#include
+
+// -----------------------------------------------------------------------------
+// Local exception-handling shim.
+//
+// The library-wide JSON_TRY / JSON_INTERNAL_CATCH macros are intentionally
+// undef'd at the end of via macro_unscope.hpp, so they are
+// not visible to consumers that include this header after json.hpp. Define a
+// header-private equivalent that respects JSON_NOEXCEPTION the same way.
+// -----------------------------------------------------------------------------
+#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
+ #define NLOHMANN_EVAL_TRY try
+ #define NLOHMANN_EVAL_CATCH_ALL catch (...)
+#else
+ #define NLOHMANN_EVAL_TRY if (true)
+ #define NLOHMANN_EVAL_CATCH_ALL if (false)
+#endif
+
+NLOHMANN_JSON_NAMESPACE_BEGIN
+
+// =============================================================================
+// Null-safe, noexcept accessors (see discussion #5129)
+//
+// These free functions provide null-safe, noexcept access to JSON values.
+// They never throw -- on any non-matching condition (the receiver is not an
+// object, the key/pointer is missing, the resolved value is null, or it has
+// the wrong type) they silently fall back to the supplied default value (for
+// `eval_value`) or to a static empty array/object (for `eval_array` /
+// `eval_object`).
+//
+// The non-member form is preferred over additional members on `basic_json`:
+// * it relies only on the public API (`is_object`, `is_array`, `is_null`,
+// `find`, `end`, `get`, `at(json_pointer)`);
+// * it does not enlarge the (already large) `basic_json` interface;
+// * it is fully resolvable via ADL (`eval_value(j, "a", 0)`);
+// * it lives in an opt-in header so users who do not need it pay nothing.
+//
+// Example:
+// auto j = from_server(); // may be null
+// int a = nlohmann::eval_value(j, "a", 0); // safe
+// auto d = nlohmann::eval_value(j,
+// "/c/d"_json_pointer,
+// std::string{}); // safe
+// for (const auto& item : nlohmann::eval_array(j, "items")) { /* ... */ }
+// =============================================================================
+
+namespace detail
+{
+
+// Singleton holding an immortalized empty JSON value of a given value_t.
+//
+// Implementation note: a plain `static const BasicJsonType instance(Kind);`
+// would trigger Clang's -Wexit-time-destructors (which the project treats as
+// an error). Instead we construct the value once into properly-aligned
+// uninitialized storage via placement-new and return a reference to it. The
+// destructor is intentionally never invoked at process exit, which is safe
+// for an empty `array`/`object` constant: it owns no resources beyond the
+// internal allocator state, and skipping its destructor avoids any
+// static-destruction-order concerns.
+//
+// NOLINTBEGIN(bugprone-exception-escape) -- BasicJsonType's default-allocator
+// constructor is non-throwing in practice for the array/object value_t we
+// instantiate this with; the noexcept here is the contract callers rely on.
+template
+const BasicJsonType& empty_json_singleton() noexcept
+{
+ // Aligned, fixed-size byte storage holding the immortalized
+ // BasicJsonType. Using std::array (rather than a C-style array)
+ // avoids `cppcoreguidelines-avoid-c-arrays` warnings and the matching
+ // flawfinder `buffer/char` heuristic.
+ using storage_t = std::array;
+ alignas(BasicJsonType) static storage_t storage{};
+
+ // Construct-once on first call. The pointer's type is a raw pointer
+ // (trivially destructible), so this static local also does not require
+ // an exit-time destructor.
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) -- intentional process-lifetime singleton
+ static const BasicJsonType* const instance =
+ ::new (static_cast(storage.data())) BasicJsonType(Kind);
+
+ return *instance;
+}
+// NOLINTEND(bugprone-exception-escape)
+
+} // namespace detail
+
+// -----------------------------------------------------------------------------
+// eval_value -- noexcept value access with default
+// -----------------------------------------------------------------------------
+
+/// @brief access a value by key with a default fallback (noexcept)
+///
+/// Returns @a default_value if any of the following holds:
+/// - @a j is not an object (null, array, string, number, boolean, ...);
+/// - @a j is an object but @a key is missing;
+/// - the value at @a key is null;
+/// - the value at @a key cannot be converted to @c ValueType.
+///
+/// Never throws.
+template
+ValueType eval_value(const BasicJsonType& j,
+ const typename BasicJsonType::object_t::key_type& key,
+ const ValueType& default_value) noexcept
+{
+ if (!j.is_object())
+ {
+ return default_value;
+ }
+
+ NLOHMANN_EVAL_TRY
+ {
+ const auto it = j.find(key);
+ if (it != j.end() && !it->is_null())
+ {
+ return it->template get();
+ }
+ }
+ NLOHMANN_EVAL_CATCH_ALL {}
+
+ return default_value;
+}
+
+/// @brief access a value by JSON Pointer with a default fallback (noexcept)
+///
+/// Returns @a default_value if any of the following holds:
+/// - @a j is not an object;
+/// - any segment of @a ptr cannot be resolved (missing, wrong type, ...);
+/// - the resolved value is null;
+/// - the resolved value cannot be converted to @c ValueType.
+///
+/// Never throws.
+template
+ValueType eval_value(const BasicJsonType& j,
+ const typename BasicJsonType::json_pointer& ptr,
+ const ValueType& default_value) noexcept
+{
+ if (!j.is_object())
+ {
+ return default_value;
+ }
+
+ NLOHMANN_EVAL_TRY
+ {
+ if (!j.contains(ptr))
+ {
+ return default_value;
+ }
+ const auto& resolved = j.at(ptr);
+ if (resolved.is_null())
+ {
+ return default_value;
+ }
+ return resolved.template get();
+ }
+ NLOHMANN_EVAL_CATCH_ALL
+ {
+ return default_value;
+ }
+}
+
+// -----------------------------------------------------------------------------
+// eval_array -- noexcept array access (returns a const reference)
+// -----------------------------------------------------------------------------
+
+/// @brief access an array by key (noexcept)
+///
+/// Returns a const reference to the array stored at @a key. Returns a const
+/// reference to a static empty array if any of the following holds:
+/// - @a j is not an object;
+/// - @a key is missing;
+/// - the value at @a key is not an array.
+///
+/// Never throws.
+template
+const BasicJsonType& eval_array(
+ const BasicJsonType& j,
+ const typename BasicJsonType::object_t::key_type& key) noexcept
+{
+ const auto& empty = detail::empty_json_singleton();
+
+ if (!j.is_object())
+ {
+ return empty;
+ }
+
+ NLOHMANN_EVAL_TRY
+ {
+ const auto it = j.find(key);
+ if (it != j.end() && it->is_array())
+ {
+ return *it;
+ }
+ }
+ NLOHMANN_EVAL_CATCH_ALL {}
+
+ return empty;
+}
+
+/// @brief access an array by JSON Pointer (noexcept)
+///
+/// Returns a const reference to the array resolved by @a ptr. Returns a const
+/// reference to a static empty array if any of the following holds:
+/// - @a j is not an object;
+/// - @a ptr cannot be resolved;
+/// - the resolved value is not an array.
+///
+/// Never throws.
+template
+const BasicJsonType& eval_array(
+ const BasicJsonType& j,
+ const typename BasicJsonType::json_pointer& ptr) noexcept
+{
+ const auto& empty = detail::empty_json_singleton();
+
+ if (!j.is_object())
+ {
+ return empty;
+ }
+
+ NLOHMANN_EVAL_TRY
+ {
+ if (!j.contains(ptr))
+ {
+ return empty;
+ }
+ const auto& resolved = j.at(ptr);
+ if (resolved.is_array())
+ {
+ return resolved;
+ }
+ }
+ NLOHMANN_EVAL_CATCH_ALL {}
+
+ return empty;
+}
+
+// -----------------------------------------------------------------------------
+// eval_object -- noexcept object access (returns a const reference)
+// -----------------------------------------------------------------------------
+
+/// @brief access an object by key (noexcept)
+///
+/// Returns a const reference to the object stored at @a key. Returns a const
+/// reference to a static empty object if any of the following holds:
+/// - @a j is not an object;
+/// - @a key is missing;
+/// - the value at @a key is not an object.
+///
+/// Never throws.
+template
+const BasicJsonType& eval_object(
+ const BasicJsonType& j,
+ const typename BasicJsonType::object_t::key_type& key) noexcept
+{
+ const auto& empty = detail::empty_json_singleton();
+
+ if (!j.is_object())
+ {
+ return empty;
+ }
+
+ NLOHMANN_EVAL_TRY
+ {
+ const auto it = j.find(key);
+ if (it != j.end() && it->is_object())
+ {
+ return *it;
+ }
+ }
+ NLOHMANN_EVAL_CATCH_ALL {}
+
+ return empty;
+}
+
+/// @brief access an object by JSON Pointer (noexcept)
+///
+/// Returns a const reference to the object resolved by @a ptr. Returns a const
+/// reference to a static empty object if any of the following holds:
+/// - @a j is not an object;
+/// - @a ptr cannot be resolved;
+/// - the resolved value is not an object.
+///
+/// Never throws.
+template
+const BasicJsonType& eval_object(
+ const BasicJsonType& j,
+ const typename BasicJsonType::json_pointer& ptr) noexcept
+{
+ const auto& empty = detail::empty_json_singleton();
+
+ if (!j.is_object())
+ {
+ return empty;
+ }
+
+ NLOHMANN_EVAL_TRY
+ {
+ if (!j.contains(ptr))
+ {
+ return empty;
+ }
+ const auto& resolved = j.at(ptr);
+ if (resolved.is_object())
+ {
+ return resolved;
+ }
+ }
+ NLOHMANN_EVAL_CATCH_ALL {}
+
+ return empty;
+}
+
+NLOHMANN_JSON_NAMESPACE_END
+
+#undef NLOHMANN_EVAL_TRY
+#undef NLOHMANN_EVAL_CATCH_ALL
diff --git a/tests/src/unit-eval.cpp b/tests/src/unit-eval.cpp
new file mode 100644
index 0000000000..4cc826f91a
--- /dev/null
+++ b/tests/src/unit-eval.cpp
@@ -0,0 +1,460 @@
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++ (supporting code)
+// | | |__ | | | | | | version 3.12.0
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+#include "doctest_compatibility.h"
+
+#include
+
+// The eval_* helpers live in an opt-in header that is intentionally NOT
+// part of the amalgamated single-include `single_include/nlohmann/json.hpp`
+// (in line with the design discussion in #5129). Skip this test file when
+// the test suite is built against the single-header amalgamation.
+//
+// Also skip when JSON_NOEXCEPTION is defined: the helpers' pointer
+// overloads remain correct under that mode, but `eval_value(j, key, T{})`
+// must call `it->template get()` whose internal `JSON_THROW` becomes
+// `std::abort()` -- there is no public, non-throwing conversion that the
+// helpers could fall back to. The helpers are therefore not exercised by
+// the no-exceptions matrix; the same approach is taken by other unit-*
+// files that exercise public APIs which can throw type_error.
+#if defined(JSON_TEST_USING_MULTIPLE_HEADERS) && JSON_TEST_USING_MULTIPLE_HEADERS \
+ && !defined(JSON_NOEXCEPTION)
+
+#include
+using nlohmann::json;
+
+#include
+
+// Tests for null-safe, noexcept accessors:
+// eval_value(j, key | ptr, default)
+// eval_array(j, key | ptr)
+// eval_object(j, key | ptr)
+//
+// See https://github.com/nlohmann/json/discussions/5129.
+//
+// NOTE on style: when calling reference-returning helpers (eval_array,
+// eval_object), the key (std::string) and json_pointer arguments are kept in
+// named local variables on purpose -- this matches the recommended user
+// style and avoids GCC's -Wdangling-reference warning that triggers when a
+// function returning a reference takes a parameter bound to a temporary
+// (the warning is a false positive here, but worth modelling correctly).
+
+TEST_CASE("eval_value with key")
+{
+ SECTION("happy path: object with matching key and type")
+ {
+ const json j = {{"a", 42}, {"s", "hello"}};
+ CHECK(nlohmann::eval_value(j, "a", 0) == 42);
+ CHECK(nlohmann::eval_value(j, "s", std::string{"x"}) == "hello");
+ }
+
+ SECTION("missing key returns default")
+ {
+ const json j = {{"a", 1}};
+ CHECK(nlohmann::eval_value(j, "missing", 7) == 7);
+ CHECK(nlohmann::eval_value(j, "missing", std::string{"def"}) == "def");
+ }
+
+ SECTION("null value at key returns default")
+ {
+ const json j = {{"a", nullptr}};
+ CHECK(nlohmann::eval_value(j, "a", 99) == 99);
+ }
+
+ SECTION("wrong type at key returns default")
+ {
+ const json j = {{"a", "not a number"}};
+ CHECK(nlohmann::eval_value(j, "a", 5) == 5);
+ }
+
+ SECTION("non-object receiver returns default")
+ {
+ const json null_j = nullptr;
+ const json arr_j = json::array({1, 2, 3});
+ const json str_j = "hello";
+ const json num_j = 42;
+ const json bool_j = true;
+
+ CHECK(nlohmann::eval_value(null_j, "a", 1) == 1);
+ CHECK(nlohmann::eval_value(arr_j, "a", 1) == 1);
+ CHECK(nlohmann::eval_value(str_j, "a", 1) == 1);
+ CHECK(nlohmann::eval_value(num_j, "a", 1) == 1);
+ CHECK(nlohmann::eval_value(bool_j, "a", 1) == 1);
+ }
+
+ SECTION("noexcept")
+ {
+ const json j = {{"a", 1}};
+ const json::object_t::key_type key = "a";
+ const int def = 0;
+ // Pre-construct the arguments so noexcept(...) measures eval_value
+ // itself, not the (possibly throwing) construction of std::string
+ // / int from the literal arguments.
+ CHECK(noexcept(nlohmann::eval_value(j, key, def)));
+ }
+}
+
+TEST_CASE("eval_value with JSON Pointer")
+{
+ SECTION("happy path: nested object")
+ {
+ const json j = {{"c", {{"d", "deep"}}}};
+ const auto ptr = json::json_pointer("/c/d");
+ CHECK(nlohmann::eval_value(j, ptr, std::string{}) == "deep");
+ }
+
+ SECTION("unresolvable pointer returns default")
+ {
+ const json j = {{"c", {{"d", "deep"}}}};
+ const auto p1 = json::json_pointer("/c/missing");
+ const auto p2 = json::json_pointer("/x/y/z");
+ CHECK(nlohmann::eval_value(j, p1, std::string{"def"}) == "def");
+ CHECK(nlohmann::eval_value(j, p2, std::string{"def"}) == "def");
+ }
+
+ SECTION("null intermediate or null resolved value returns default")
+ {
+ const json j1 = {{"c", nullptr}};
+ const auto pcd = json::json_pointer("/c/d");
+ // /c is null -> walking to /c/d is unresolvable -> default
+ CHECK(nlohmann::eval_value(j1, pcd, std::string{"def"}) == "def");
+
+ const json j2 = {{"c", {{"d", nullptr}}}};
+ CHECK(nlohmann::eval_value(j2, pcd, std::string{"def"}) == "def");
+ }
+
+ SECTION("wrong resolved type returns default")
+ {
+ const json j = {{"a", "not a number"}};
+ const auto pa = json::json_pointer("/a");
+ CHECK(nlohmann::eval_value(j, pa, 5) == 5);
+ }
+
+ SECTION("non-object receiver returns default")
+ {
+ const json null_j = nullptr;
+ const json arr_j = json::array({1, 2, 3});
+ const auto pa = json::json_pointer("/a");
+ const auto p0 = json::json_pointer("/0");
+
+ CHECK(nlohmann::eval_value(null_j, pa, 7) == 7);
+ CHECK(nlohmann::eval_value(arr_j, p0, 7) == 7);
+ }
+
+ SECTION("noexcept")
+ {
+ const json j = {{"a", 1}};
+ const auto ptr = json::json_pointer("/a");
+ CHECK(noexcept(nlohmann::eval_value(j, ptr, 0)));
+ }
+}
+
+TEST_CASE("eval_array with key")
+{
+ // Keep the key alive in a named variable: eval_array returns a
+ // reference, so binding it to `const auto&` while passing a temporary
+ // std::string for the key would otherwise trip GCC's
+ // -Wdangling-reference (false positive here, but worth modelling).
+ const json::object_t::key_type k_items = "items";
+ const json::object_t::key_type k_missing = "missing";
+ const json::object_t::key_type k_x = "x";
+ const json::object_t::key_type k_y = "y";
+
+ SECTION("happy path: returns const reference to array")
+ {
+ const json j = {{"items", {1, 2, 3}}};
+ const auto& arr = nlohmann::eval_array(j, k_items);
+ REQUIRE(arr.is_array());
+ CHECK(arr.size() == 3);
+ CHECK(arr[0] == 1);
+ CHECK(arr[2] == 3);
+ }
+
+ SECTION("range-based for loop is safe")
+ {
+ const json j = {{"items", {10, 20, 30}}};
+ int sum = 0;
+ const auto& arr = nlohmann::eval_array(j, k_items);
+ for (const auto& item : arr)
+ {
+ sum += item.template get();
+ }
+ CHECK(sum == 60);
+ }
+
+ SECTION("missing key returns empty array")
+ {
+ const json j = {{"a", 1}};
+ const auto& arr = nlohmann::eval_array(j, k_missing);
+ REQUIRE(arr.is_array());
+ CHECK(arr.empty());
+ }
+
+ SECTION("wrong type at key returns empty array")
+ {
+ const json j = {{"items", "not an array"}};
+ const auto& arr = nlohmann::eval_array(j, k_items);
+ REQUIRE(arr.is_array());
+ CHECK(arr.empty());
+ }
+
+ SECTION("non-object receiver returns empty array")
+ {
+ const json null_j = nullptr;
+ const json arr_j = json::array({1, 2, 3});
+ const json num_j = 42;
+
+ const auto& a1 = nlohmann::eval_array(null_j, k_items);
+ const auto& a2 = nlohmann::eval_array(arr_j, k_items);
+ const auto& a3 = nlohmann::eval_array(num_j, k_items);
+
+ CHECK(a1.is_array());
+ CHECK(a1.empty());
+ CHECK(a2.is_array());
+ CHECK(a2.empty());
+ CHECK(a3.is_array());
+ CHECK(a3.empty());
+ }
+
+ SECTION("static empty array reference is stable")
+ {
+ const json j = {{"a", 1}};
+ const auto& a = nlohmann::eval_array(j, k_x);
+ const auto& b = nlohmann::eval_array(j, k_y);
+ // Both should reference the same static singleton.
+ CHECK(&a == &b);
+ }
+
+ SECTION("noexcept")
+ {
+ const json j = {{"items", json::array()}};
+ CHECK(noexcept(nlohmann::eval_array(j, k_items)));
+ }
+}
+
+TEST_CASE("eval_array with JSON Pointer")
+{
+ const auto p_entries = json::json_pointer("/response/data/entries");
+ const auto p_xyz = json::json_pointer("/x/y/z");
+ const auto p_x = json::json_pointer("/x");
+ const auto p_a = json::json_pointer("/a");
+
+ SECTION("happy path: nested array")
+ {
+ const json j = {{"response", {{"data", {{"entries", {1, 2, 3}}}}}}};
+ const auto& arr = nlohmann::eval_array(j, p_entries);
+ REQUIRE(arr.is_array());
+ CHECK(arr.size() == 3);
+ }
+
+ SECTION("unresolvable pointer returns empty array")
+ {
+ const json j = {{"a", 1}};
+ const auto& arr = nlohmann::eval_array(j, p_xyz);
+ REQUIRE(arr.is_array());
+ CHECK(arr.empty());
+ }
+
+ SECTION("wrong resolved type returns empty array")
+ {
+ const json j = {{"x", "not an array"}};
+ const auto& arr = nlohmann::eval_array(j, p_x);
+ REQUIRE(arr.is_array());
+ CHECK(arr.empty());
+ }
+
+ SECTION("non-object receiver returns empty array")
+ {
+ const json null_j = nullptr;
+ const auto& arr = nlohmann::eval_array(null_j, p_a);
+ CHECK(arr.is_array());
+ CHECK(arr.empty());
+ }
+
+ SECTION("noexcept")
+ {
+ const json j = {{"a", json::array()}};
+ CHECK(noexcept(nlohmann::eval_array(j, p_a)));
+ }
+}
+
+TEST_CASE("eval_object with key")
+{
+ const json::object_t::key_type k_meta = "meta";
+ const json::object_t::key_type k_missing = "missing";
+ const json::object_t::key_type k_x = "x";
+ const json::object_t::key_type k_y = "y";
+
+ SECTION("happy path: returns const reference to object")
+ {
+ const json j = {{"meta", {{"k", 1}, {"v", 2}}}};
+ const auto& obj = nlohmann::eval_object(j, k_meta);
+ REQUIRE(obj.is_object());
+ CHECK(obj.size() == 2);
+ CHECK(obj.at("k") == 1);
+ }
+
+ SECTION("range-based for over items() is safe")
+ {
+ const json j = {{"meta", {{"a", 1}, {"b", 2}}}};
+ int count = 0;
+ const auto& obj = nlohmann::eval_object(j, k_meta);
+ for (const auto& kv : obj.items())
+ {
+ (void)kv;
+ ++count;
+ }
+ CHECK(count == 2);
+ }
+
+ SECTION("missing key returns empty object")
+ {
+ const json j = {{"a", 1}};
+ const auto& obj = nlohmann::eval_object(j, k_missing);
+ REQUIRE(obj.is_object());
+ CHECK(obj.empty());
+ }
+
+ SECTION("wrong type at key returns empty object")
+ {
+ const json j = {{"meta", "not an object"}};
+ const auto& obj = nlohmann::eval_object(j, k_meta);
+ REQUIRE(obj.is_object());
+ CHECK(obj.empty());
+ }
+
+ SECTION("non-object receiver returns empty object")
+ {
+ const json null_j = nullptr;
+ const json arr_j = json::array({1, 2, 3});
+
+ const auto& o1 = nlohmann::eval_object(null_j, k_meta);
+ const auto& o2 = nlohmann::eval_object(arr_j, k_meta);
+
+ CHECK(o1.is_object());
+ CHECK(o1.empty());
+ CHECK(o2.is_object());
+ CHECK(o2.empty());
+ }
+
+ SECTION("static empty object reference is stable")
+ {
+ const json j = {{"a", 1}};
+ const auto& a = nlohmann::eval_object(j, k_x);
+ const auto& b = nlohmann::eval_object(j, k_y);
+ CHECK(&a == &b);
+ }
+
+ SECTION("array vs object empty singletons differ")
+ {
+ const json j = {{"a", 1}};
+ const auto& arr = nlohmann::eval_array(j, k_x);
+ const auto& obj = nlohmann::eval_object(j, k_x);
+ CHECK(arr.is_array());
+ CHECK(obj.is_object());
+ }
+
+ SECTION("noexcept")
+ {
+ const json j = {{"meta", json::object()}};
+ CHECK(noexcept(nlohmann::eval_object(j, k_meta)));
+ }
+}
+
+TEST_CASE("eval_object with JSON Pointer")
+{
+ const auto p_cd = json::json_pointer("/c/d");
+ const auto p_xyz = json::json_pointer("/x/y/z");
+ const auto p_x = json::json_pointer("/x");
+ const auto p_a = json::json_pointer("/a");
+
+ SECTION("happy path: nested object")
+ {
+ const json j = {{"c", {{"d", {{"e", 1}}}}}};
+ const auto& obj = nlohmann::eval_object(j, p_cd);
+ REQUIRE(obj.is_object());
+ CHECK(obj.at("e") == 1);
+ }
+
+ SECTION("unresolvable pointer returns empty object")
+ {
+ const json j = {{"a", 1}};
+ const auto& obj = nlohmann::eval_object(j, p_xyz);
+ REQUIRE(obj.is_object());
+ CHECK(obj.empty());
+ }
+
+ SECTION("wrong resolved type returns empty object")
+ {
+ const json j = {{"x", json::array({1, 2})}};
+ const auto& obj = nlohmann::eval_object(j, p_x);
+ REQUIRE(obj.is_object());
+ CHECK(obj.empty());
+ }
+
+ SECTION("non-object receiver returns empty object")
+ {
+ const json null_j = nullptr;
+ const auto& obj = nlohmann::eval_object(null_j, p_a);
+ CHECK(obj.is_object());
+ CHECK(obj.empty());
+ }
+
+ SECTION("noexcept")
+ {
+ const json j = {{"a", json::object()}};
+ CHECK(noexcept(nlohmann::eval_object(j, p_a)));
+ }
+}
+
+TEST_CASE("eval functions end-to-end scenario from discussion 5129")
+{
+ SECTION("safe access on a possibly-null payload")
+ {
+ // Simulate `auto received = from_server();` with a null payload.
+ const json received = nullptr;
+
+ const auto p_cd = json::json_pointer("/c/d");
+ const int a = nlohmann::eval_value(received, "a", 0);
+ const auto d = nlohmann::eval_value(received, p_cd, std::string{});
+
+ CHECK(a == 0);
+ CHECK(d.empty());
+
+ // Range-based for is still safe on a null receiver.
+ const json::object_t::key_type k_items = "items";
+ const auto& items = nlohmann::eval_array(received, k_items);
+ int count = 0;
+ for (const auto& item : items)
+ {
+ (void)item;
+ ++count;
+ }
+ CHECK(count == 0);
+ }
+
+ SECTION("safe access on a partial payload")
+ {
+ const json received = {{"a", 5}}; // no "c", no "items"
+ const auto p_cd = json::json_pointer("/c/d");
+ const json::object_t::key_type k_items = "items";
+
+ CHECK(nlohmann::eval_value(received, "a", 0) == 5);
+ CHECK(nlohmann::eval_value(received, p_cd, std::string{"fallback"}) == "fallback");
+ CHECK(nlohmann::eval_array(received, k_items).empty());
+ }
+
+ SECTION("ADL works without explicit namespace qualification")
+ {
+ const json j = {{"a", 7}};
+ // ADL: argument-dependent lookup finds nlohmann::eval_value via `j`.
+ CHECK(eval_value(j, "a", 0) == 7);
+ }
+}
+
+#endif // JSON_TEST_USING_MULTIPLE_HEADERS && !JSON_NOEXCEPTION