Skip to content

Add null-safe noexcept accessors eval_value/eval_array/eval_object#5193

Open
gitpaladin wants to merge 1 commit into
nlohmann:developfrom
gitpaladin:feature/eval-accessors
Open

Add null-safe noexcept accessors eval_value/eval_array/eval_object#5193
gitpaladin wants to merge 1 commit into
nlohmann:developfrom
gitpaladin:feature/eval-accessors

Conversation

@gitpaladin
Copy link
Copy Markdown

Summary

Introduces an opt-in header <nlohmann/eval.hpp> with three free function templates that complement basic_json::value() with semantics designed for untrusted JSON payloads:

Function Behaviour
eval_value(j, key|ptr, default) noexcept, returns default on any non-matching condition (non-object receiver, missing key/path, null resolved value, wrong type, conversion failure).
eval_array(j, key|ptr) noexcept, returns a const& to a static empty array on any non-matching condition.
eval_object(j, key|ptr) noexcept, returns a const& to a static empty object on any non-matching condition.

Follows the design agreed in #5129.

Why non-member, opt-in header

Per your preference in the discussion ("I definitely prefer this over an extension of the (already bloated) basic_json API"):

  • Non-member functions in namespace nlohmann so ADL works without explicit qualification (eval_value(j, ...) just works).
  • Dedicated opt-in header — basic_json's public surface is not extended.
  • Uses only the public API (is_object, is_array, is_null, find, end, get, contains(json_pointer), at(json_pointer)).
  • Pointer overloads guard with j.contains(ptr) before j.at(ptr), so they remain correct under JSON_NOEXCEPTION too (where at() would otherwise abort).
  • Static empty array/object fallbacks are returned by const reference via Meyers' singletons — no per-call allocation, thread-safe since C++11.
  • A header-private NLOHMANN_EVAL_TRY/NLOHMANN_EVAL_CATCH_ALL pair mirrors JSON_TRY/JSON_INTERNAL_CATCH, since those macros are #undef'd at the end of json.hpp via macro_unscope.hpp and are not visible to consumers.

What is added

  • include/nlohmann/eval.hpp — 6 overloads (key / json_pointer × value / array / object).
  • tests/src/unit-eval.cpp — 7 test cases, 79 assertions, picked up automatically by the existing file(GLOB src/unit-*.cpp) in tests/CMakeLists.txt.
  • docs/mkdocs/docs/api/eval.md — API page mirroring basic_json/value.md style.

What is not changed

Local test matrix

Toolchain Standards Result
MSVC cl (VS 2026) C++17 7/7 cases, 79/79 assertions PASS, 0 warnings
MSYS2 g++ 15.2 C++11 / C++17 / C++20 PASS each, 0 warnings
MSYS2 clang++ 21.1 C++11 / C++17 / C++20 PASS each, 0 warnings (incl. -Wshadow -Wconversion -Wsign-conversion -Wold-style-cast)
g++ 15.2 + -fno-exceptions -DJSON_NOEXCEPTION C++17 smoke test PASS (doctest's own limitations apply to the full suite, same as test-disabled_exceptions)

Checklist

  • Changes described above.
  • References discussion Feature Discussion: Null-safe, noexcept accessors — `eval_value`, `eval_array`, `eval_object` #5129.
  • New code covered by tests in tests/src/unit-eval.cpp (7 cases / 79 assertions).
  • API documentation page added under docs/mkdocs/docs/api/.
  • make amalgamate — not run yet because the new header is intentionally not part of the single-include. If you'd like it bundled, I'll extend the amalgamate config and rerun. Will also rerun should the CI's amalgamation check flag any astyle drift.

Marked as draft so you can pre-screen the direction; happy to address feedback (naming, single-include bundling, anything else) before un-drafting.

Introduce a small, opt-in header <nlohmann/eval.hpp> providing three free
function templates that complement basic_json::value() with semantics
designed for untrusted JSON payloads:

  - eval_value(j, key|ptr, default)  -- noexcept, returns default on any
    non-matching condition (non-object receiver, missing key/path,
    null resolved value, wrong type, conversion failure).
  - eval_array(j, key|ptr)           -- noexcept, returns const ref to
    a static empty array on any non-matching condition.
  - eval_object(j, key|ptr)          -- noexcept, returns const ref to
    a static empty object on any non-matching condition.

Design follows the discussion on nlohmann#5129:

  * Implemented as non-member functions in namespace nlohmann so that
    ADL works without explicit qualification (eval_value(j, ...) just
    works).
  * Lives in a dedicated opt-in header so basic_json's already large
    public API is not extended (per maintainer preference in the
    discussion).
  * Uses only the public API of basic_json (is_object, is_array, find,
    end, get, contains(json_pointer), at(json_pointer)). The pointer
    overloads guard with j.contains(ptr) before j.at(ptr), which is
    correct under JSON_NOEXCEPTION too (where at() would otherwise
    abort instead of throwing).
  * Static empty array/object fallbacks are returned by const reference
    via Meyers' singletons, so they incur no per-call allocation and
    are thread-safe.
  * Header-private NLOHMANN_EVAL_TRY / NLOHMANN_EVAL_CATCH_ALL macros
    mirror the JSON_TRY / JSON_INTERNAL_CATCH semantics (the library's
    own macros are intentionally undef'd at the end of json.hpp via
    macro_unscope.hpp and are therefore unavailable to consumers).

Tests (tests/src/unit-eval.cpp): 7 test cases, 79 assertions, covering
happy paths, missing keys/pointers, null resolved values, wrong
resolved types, non-object receivers, ADL invocation, stable
singleton identity, range-based for safety, and noexcept(...) probes.

Documentation: docs/mkdocs/docs/api/eval.md describes the API,
semantics, comparison with value(), and design notes.

Signed-off-by: gitpaladin <alexander.wuhan@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant