Files
json/include/nlohmann/detail/view/pointer.hpp
T
Niels Lohmann 1f36206954 json_view: exceptions name the path and the bytes of the value
With JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS, the exceptions of views
now carry the same context as those of basic_json: "(/path) " and
"(bytes a-b) " in front of the message, in basic_json's format (with
JSON_DIAGNOSTICS, no context for the root). Before, views threw without any
context, and since get<T>() reads the view directly, conversions had lost
even the relative path they used to get through materialize().

The throw helpers of detail/view/errors.hpp take an error_site (document
and node; empty without the two macros). Only on the error path, the path is
found by descending from the root (pruned by subtree extent before the first
edit, a full walk through edited sequences after), and the bytes as
materialize() sets them (position_source; a container's end by following the
chain of last elements and scanning to the closing brackets). from_json() for
views and the strict enum macro put the same context in front through
exception_prefix(). Iterators keep their container only under the two
macros, for key(). Without the macros, the accessors compile to the same
code as before.

Tests compare every value's exceptions with those of the parsed or
materialized basic_json value, including edited documents; the checks that
were disabled under the two macros run again.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 14:01:45 +01:00

139 lines
4.8 KiB
C++

// __ _____ _____ _____
// __| | __| | | | 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> // uint64_t
#include <limits> // numeric_limits
#include <string> // string, to_string
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
/// what resolving a JSON pointer does where it cannot continue
enum class pointer_mode
{
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
checked, ///< as basic_json::at(): out_of_range.401/403
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
contains, ///< as basic_json::contains(): no exceptions at all
};
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
using index_status = detail::array_index_status;
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them
template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept
{
return detail::parse_array_index(s, idx);
}
/// throws the exception json_pointer::array_index throws for this status
template<typename StringType>
/// (without a context, as json_pointer::array_index)
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
{
switch (status)
{
case index_status::leading_zero:
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_a_number:
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
case index_status::exceeds_size_type:
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved:
case index_status::ok:
default:
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
}
}
/*!
@brief resolve the reference tokens of a JSON pointer, starting at a view
The exceptions are those basic_json throws for the same pointer; where
basic_json's behavior is undefined (a missing key or an index out of range
with const operator[]), the result is a discarded view.
*/
template<typename View, typename Tokens>
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
using string_view_t = typename View::string_view_t;
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
for (const auto& token : tokens)
{
if (cur.is_object())
{
const auto it = cur.find(string_view_t(token.data(), token.size()));
if (it == cur.end())
{
if (mode == pointer_mode::checked)
{
throw_key_not_found(site_of(cur), token);
}
return View();
}
cur = *it;
}
else if (cur.is_array())
{
if (token.size() == 1 && token[0] == '-')
{
if (throwing)
{
throw_array_index_past_end(site_of(cur), cur.size());
}
return View();
}
std::size_t idx = 0;
const index_status status = array_index(token, idx);
if (status != index_status::ok)
{
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
if (throwing || (mode == pointer_mode::value && parse_error))
{
throw_array_index_error(status, token);
}
return View();
}
if (idx >= cur.size())
{
if (mode == pointer_mode::checked)
{
throw_array_index_out_of_range(site_of(cur), idx);
}
return View();
}
cur = cur[idx];
}
else
{
if (throwing)
{
throw_out_of_range(site_of(cur), exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
}
return View();
}
}
return cur;
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END