Files
json/include/nlohmann/detail/view/pointer.hpp
Niels Lohmann 85f414ae7a Add values and JSON pointers to json_view
basic_json_view gains get<T>(), get_to(), value() with keys and JSON
pointers, and operator[], at(), and contains() with JSON pointers, plus
two functions basic_json has no counterpart for:

- get_string(): the string without a copy (a string_view into the source,
  or into the decoded strings for strings with escapes)
- number_token(): the text of a number as it appears in the source

get<T>() converts arithmetic types, strings (also string_view_t),
std::nullptr_t, std::vector, maps with string keys, and views directly;
floats are converted from the digit layout recorded by the parser with the
library's conversion chain, so the values are bit-identical to parse().
Other types, including user types with from_json(), go through
materialize().

The exceptions are those of basic_json, message included. Where const
basic_json has undefined behavior (a missing key or an index out of range
with operator[] and a JSON pointer), the result is a discarded view;
value() returns the default wherever basic_json catches out_of_range, and
contains() never throws. Array indices of JSON pointers follow
json_pointer's rules (parse_error.106/109, out_of_range.404/410).

Tests compare the conversions of 2,000 generated documents, 20,000 float
tokens (double and float, bit for bit), and every JSON pointer of 1,000
documents with basic_json, and the exceptions for malformed pointers.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 14:02:52 +02:00

172 lines
5.6 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 from a reference token
enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// 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
{
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))
{
return index_status::not_number;
}
if (s.empty())
{
return index_status::unresolved;
}
std::uint64_t v = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)()))
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
}
/// throws the exception json_pointer::array_index throws for this status
template<typename StringType>
[[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(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_number:
throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::too_large:
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved:
case index_status::ok:
default:
throw_out_of_range(404, 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_out_of_range(403, concat("key '", token, "' not found"));
}
return View();
}
cur = *it;
}
else if (cur.is_array())
{
if (token.size() == 1 && token[0] == '-')
{
if (throwing)
{
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
}
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_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_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
}
return View();
}
cur = cur[idx];
}
else
{
if (throwing)
{
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
}
return View();
}
}
return cur;
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END