mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 22:47:13 +00:00
Give basic_json_view the read-only access functions of basic_json: operator[] and at() with keys and indices, front()/back(), find(), contains(), count(), begin()/end() and cbegin()/cend(), items() with structured bindings from C++17 on, and type_name(). Exceptions have the ids and messages of the const functions of basic_json. Where basic_json has undefined behavior the view answers safely: operator[] with a missing key or an out-of-range index returns a discarded view, and front()/back() of an empty container throw invalid_iterator.214. Objects are iterated in document order, and all members are visited; duplicate-key lookups find the first member (as yyjson and simdjson do), while parse(), materialize(), and the map conversions keep the last value, as parse() does. Keys of up to 16 bytes are compared with two overlapping loads. Add value conversions: get<T>()/get_to() for arithmetic types, bool, nullptr_t, strings (std::basic_string copied, string_view_t without a copy), BasicJsonType, views, std::vector, and maps with string keys; get_string() for the string without a copy; number_token() for the number exactly as written in the source; value() with keys and JSON pointers; and operator[]/at()/ contains() with JSON pointers. Everything else, including types with from_json(), goes through materialize() of that subtree. get<T>() of arithmetic types is inlined down to the conversion, so reading an integer needs no call. detail::json_pointer_access exposes a pointer's reference tokens to code outside basic_json. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1197 lines
44 KiB
C++
1197 lines
44 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 <algorithm> // all_of
|
|
#include <cctype> // isdigit
|
|
#include <cerrno> // errno, ERANGE
|
|
#include <cstdlib> // strtoull
|
|
#ifndef JSON_NO_IO
|
|
#include <iosfwd> // ostream
|
|
#endif // JSON_NO_IO
|
|
#include <limits> // max
|
|
#include <numeric> // accumulate
|
|
#include <set> // set
|
|
#include <string> // string
|
|
#include <utility> // move
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/detail/exceptions.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
#include <nlohmann/detail/string_concat.hpp>
|
|
#include <nlohmann/detail/string_escape.hpp>
|
|
#include <nlohmann/detail/string_utils.hpp>
|
|
#include <nlohmann/detail/value_t.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
|
|
namespace detail
|
|
{
|
|
struct json_pointer_access;
|
|
} // namespace detail
|
|
|
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/
|
|
template<typename RefStringType>
|
|
class json_pointer
|
|
{
|
|
// allow basic_json to access private members
|
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
|
friend class basic_json;
|
|
|
|
template<typename>
|
|
friend class json_pointer;
|
|
|
|
friend struct detail::json_pointer_access;
|
|
|
|
template<typename T>
|
|
struct string_t_helper
|
|
{
|
|
using type = T;
|
|
};
|
|
|
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
|
struct string_t_helper<NLOHMANN_BASIC_JSON_TPL>
|
|
{
|
|
using type = StringType;
|
|
};
|
|
|
|
public:
|
|
// for backwards compatibility accept BasicJsonType
|
|
using string_t = typename string_t_helper<RefStringType>::type;
|
|
|
|
/// @brief create JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/
|
|
explicit json_pointer(const string_t& s = "")
|
|
: reference_tokens(split(s))
|
|
{}
|
|
|
|
/// @brief return a string representation of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
|
string_t to_string() const
|
|
{
|
|
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
|
string_t{},
|
|
[](const string_t& a, const string_t& b)
|
|
{
|
|
return detail::concat<string_t>(a, '/', detail::escape(b));
|
|
});
|
|
}
|
|
|
|
/// @brief return a string representation of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
|
|
operator string_t() const
|
|
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
= delete;
|
|
#else
|
|
{
|
|
return to_string();
|
|
}
|
|
#endif
|
|
|
|
#ifndef JSON_NO_IO
|
|
/// @brief write string representation of the JSON pointer to stream
|
|
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
|
friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr)
|
|
{
|
|
o << ptr.to_string();
|
|
return o;
|
|
}
|
|
#endif
|
|
|
|
/// @brief append another JSON pointer at the end of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
|
json_pointer& operator/=(const json_pointer& ptr)
|
|
{
|
|
reference_tokens.insert(reference_tokens.end(),
|
|
ptr.reference_tokens.begin(),
|
|
ptr.reference_tokens.end());
|
|
return *this;
|
|
}
|
|
|
|
/// @brief append an unescaped reference token at the end of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
|
json_pointer& operator/=(string_t token)
|
|
{
|
|
push_back(std::move(token));
|
|
return *this;
|
|
}
|
|
|
|
/// @brief append an array index at the end of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
|
json_pointer& operator/=(std::size_t array_idx)
|
|
{
|
|
return *this /= detail::to_string<string_t>(array_idx);
|
|
}
|
|
|
|
/// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
|
friend json_pointer operator/(const json_pointer& lhs,
|
|
const json_pointer& rhs)
|
|
{
|
|
return json_pointer(lhs) /= rhs;
|
|
}
|
|
|
|
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
|
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
|
{
|
|
return json_pointer(lhs) /= std::move(token);
|
|
}
|
|
|
|
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
|
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
|
{
|
|
return json_pointer(lhs) /= array_idx;
|
|
}
|
|
|
|
/// @brief returns the parent of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/
|
|
json_pointer parent_pointer() const
|
|
{
|
|
if (empty())
|
|
{
|
|
return *this;
|
|
}
|
|
|
|
json_pointer res = *this;
|
|
res.pop_back();
|
|
return res;
|
|
}
|
|
|
|
/// @brief remove first reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/pop_front/
|
|
void pop_front()
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
reference_tokens.erase(reference_tokens.begin());
|
|
}
|
|
|
|
/// @brief return first reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/front/
|
|
const string_t& front() const
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
return reference_tokens.front();
|
|
}
|
|
|
|
/// @brief append an unescaped token at the start of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_front/
|
|
void push_front(const string_t& token)
|
|
{
|
|
reference_tokens.insert(reference_tokens.begin(), token);
|
|
}
|
|
|
|
/// @brief append an unescaped token at the start of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_front/
|
|
void push_front(string_t&& token)
|
|
{
|
|
reference_tokens.insert(reference_tokens.begin(), std::move(token));
|
|
}
|
|
|
|
/// @brief remove last reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/pop_back/
|
|
void pop_back()
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
reference_tokens.pop_back();
|
|
}
|
|
|
|
/// @brief return last reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/back/
|
|
const string_t& back() const
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
return reference_tokens.back();
|
|
}
|
|
|
|
/// @brief append an unescaped token at the end of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_back/
|
|
void push_back(const string_t& token)
|
|
{
|
|
reference_tokens.push_back(token);
|
|
}
|
|
|
|
/// @brief append an unescaped token at the end of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_back/
|
|
void push_back(string_t&& token)
|
|
{
|
|
reference_tokens.push_back(std::move(token));
|
|
}
|
|
|
|
/// @brief return whether pointer points to the root document
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/empty/
|
|
bool empty() const noexcept
|
|
{
|
|
return reference_tokens.empty();
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@brief result of @ref parse_array_index
|
|
|
|
@ref array_index maps each value to the corresponding parse_error/out_of_range
|
|
exception; @ref contains and @ref get_checked_or_null, which must not throw for
|
|
an out-of-range or unrepresentable index, switch on it directly instead.
|
|
*/
|
|
enum class array_index_status
|
|
{
|
|
ok, ///< @a s is a valid, representable array index
|
|
leading_zero, ///< @a s begins with '0' but has more than one character
|
|
not_a_number, ///< @a s does not begin with a digit
|
|
unresolved, ///< @a s could not be converted to an integer
|
|
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
|
};
|
|
|
|
/*!
|
|
@param[in] s reference token to be converted into an array index
|
|
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
|
is returned; left unchanged otherwise
|
|
|
|
@return whether @a s is a valid array index, and if not, why
|
|
|
|
@note this function never throws; @ref array_index and the callers that must not
|
|
throw (@ref contains, @ref get_checked_or_null) build on it instead of each
|
|
re-implementing the RFC 6901 digit rules and the @a size_type range check
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept
|
|
{
|
|
using size_type = typename BasicJsonType::size_type;
|
|
|
|
// error condition (cf. RFC 6901, Sect. 4)
|
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
|
{
|
|
return array_index_status::leading_zero;
|
|
}
|
|
|
|
// error condition (cf. RFC 6901, Sect. 4)
|
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
|
{
|
|
return array_index_status::not_a_number;
|
|
}
|
|
|
|
const char* p = s.data();
|
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
|
errno = 0; // strtoull doesn't reset errno
|
|
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
|
if (p == p_end // invalid input or empty string
|
|
|| errno == ERANGE // out of range
|
|
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
|
{
|
|
return array_index_status::unresolved;
|
|
}
|
|
|
|
// the index does not fit into size_type; on 64-bit platforms this is
|
|
// only SIZE_MAX itself (see #2203 and #5395)
|
|
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
|
{
|
|
return array_index_status::exceeds_size_type;
|
|
}
|
|
|
|
idx = static_cast<size_type>(res);
|
|
return array_index_status::ok;
|
|
}
|
|
|
|
/*!
|
|
@param[in] s reference token to be converted into an array index
|
|
|
|
@return integer representation of @a s
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index begins not with a digit
|
|
@throw out_of_range.404 if string @a s could not be converted to an integer
|
|
@throw out_of_range.410 if an array index exceeds size_type
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static typename BasicJsonType::size_type array_index(const string_t& s)
|
|
{
|
|
typename BasicJsonType::size_type idx{};
|
|
switch (parse_array_index<BasicJsonType>(s, idx))
|
|
{
|
|
// the branches differ in their messages, not after JSON_THROW's expansion
|
|
// NOLINTNEXTLINE(bugprone-branch-clone)
|
|
case array_index_status::leading_zero:
|
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
|
case array_index_status::not_a_number:
|
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
|
case array_index_status::unresolved:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
|
case array_index_status::exceeds_size_type:
|
|
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr));
|
|
case array_index_status::ok:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
json_pointer top() const
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
json_pointer result = *this;
|
|
result.reference_tokens = {reference_tokens[0]};
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@brief the reference token sequences that denote arrays
|
|
|
|
@ref unflatten collects the pointer prefixes that have a reference token 0
|
|
among their children; @ref get_and_create creates arrays exactly below
|
|
those prefixes and objects everywhere else. Deciding this up front keeps
|
|
the result independent of the order in which the flattened object is
|
|
iterated, which is unspecified for some object types.
|
|
*/
|
|
using array_parents_t = std::set<std::vector<string_t>>;
|
|
|
|
/*!
|
|
@brief create and return a reference to the pointed to value
|
|
|
|
Complexity: Linear in the number of reference tokens.
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if array index is not a number
|
|
@throw type_error.313 if value cannot be unflattened
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType& get_and_create(BasicJsonType& j, const array_parents_t& array_parents) const
|
|
{
|
|
auto* result = &j;
|
|
|
|
// the reference tokens that have been consumed so far; used to look up
|
|
// whether the value to be created below is an array or an object
|
|
std::vector<string_t> prefix;
|
|
|
|
// in case no reference tokens exist, return a reference to the JSON value
|
|
// j which will be overwritten by a primitive value
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (result->type())
|
|
{
|
|
case detail::value_t::null:
|
|
{
|
|
if (array_parents.find(prefix) != array_parents.end())
|
|
{
|
|
// some reference token below this position is 0, so the
|
|
// value is an array
|
|
result = &result->operator[](array_index<BasicJsonType>(reference_token));
|
|
}
|
|
else
|
|
{
|
|
// start a new object otherwise
|
|
result = &result->operator[](reference_token);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::object:
|
|
{
|
|
// create an entry in the object
|
|
result = &result->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
// create an entry in the array
|
|
result = &result->operator[](array_index<BasicJsonType>(reference_token));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
The following code is only reached if there exists a reference
|
|
token _and_ the current value is primitive. In this case, we have
|
|
an error situation, because primitive values may only occur as
|
|
a single value; that is, with an empty list of reference tokens.
|
|
*/
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
|
}
|
|
|
|
prefix.push_back(reference_token);
|
|
}
|
|
|
|
return *result;
|
|
}
|
|
|
|
/*!
|
|
@brief return a reference to the pointed to value
|
|
|
|
@note This version does not throw if a value is not present, but tries to
|
|
create nested values instead. For instance, calling this function
|
|
with pointer `"/this/that"` on a null value is equivalent to calling
|
|
`operator[]("this").operator[]("that")` on that value, effectively
|
|
changing the null value to an object.
|
|
|
|
@param[in] ptr a JSON value
|
|
|
|
@return reference to the JSON value pointed to by the JSON pointer
|
|
|
|
Complexity: Linear in the length of the JSON pointer.
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
@throw out_of_range.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType& get_unchecked(BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
// convert null values to arrays or objects before continuing
|
|
if (ptr->is_null())
|
|
{
|
|
// check if the reference token is a number
|
|
const bool nums =
|
|
std::all_of(reference_token.begin(), reference_token.end(),
|
|
[](const unsigned char x)
|
|
{
|
|
return std::isdigit(x);
|
|
});
|
|
|
|
// change value to an array for numbers or "-" or to object otherwise
|
|
*ptr = (nums || reference_token == "-")
|
|
? detail::value_t::array
|
|
: detail::value_t::object;
|
|
}
|
|
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// use unchecked object access
|
|
ptr = &ptr->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (reference_token == "-")
|
|
{
|
|
// explicitly treat "-" as index beyond the end
|
|
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
|
}
|
|
else
|
|
{
|
|
// convert array index to number; unchecked access
|
|
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@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.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType& get_checked(BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// note: at performs range check
|
|
ptr = &ptr->at(reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
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));
|
|
}
|
|
|
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
|
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
|
"array index ", std::to_string(idx), " is out of range"), ptr));
|
|
}
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@brief return a const reference to the pointed to value
|
|
|
|
@param[in] ptr a JSON value
|
|
|
|
@return const reference to the JSON value pointed to by the JSON
|
|
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.109 if an array index was not a number
|
|
@throw out_of_range.402 if the array index '-' is used
|
|
@throw out_of_range.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// use unchecked object access; the const operator[]
|
|
// asserts that the key exists
|
|
ptr = &ptr->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" cannot be used for const access
|
|
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; the const operator[]
|
|
// asserts that the index exists
|
|
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@brief return a pointer to the pointed to value, or `nullptr` if the
|
|
pointer cannot be resolved because a key is missing, an array
|
|
index is out of range, or the array index is "-"
|
|
|
|
@note unlike get_checked(), this never throws for those cases, so it
|
|
can be used to implement a non-throwing fallback (e.g. value())
|
|
that also works when exceptions are disabled
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
*/
|
|
template<typename BasicJsonType>
|
|
const BasicJsonType* get_checked_or_null(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
const auto it = ptr->find(reference_token);
|
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ptr = &*it;
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
return nullptr;
|
|
}
|
|
|
|
// a malformed index throws parse_error.106/109; an
|
|
// index that is syntactically valid but cannot be
|
|
// represented (out_of_range.404/410) is treated like an
|
|
// out-of-range index below
|
|
typename BasicJsonType::size_type idx{};
|
|
switch (parse_array_index<BasicJsonType>(reference_token, idx))
|
|
{
|
|
// the branches differ in their messages, not after JSON_THROW's expansion
|
|
// NOLINTNEXTLINE(bugprone-branch-clone)
|
|
case array_index_status::leading_zero:
|
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
|
case array_index_status::not_a_number:
|
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
|
case array_index_status::unresolved:
|
|
case array_index_status::exceeds_size_type:
|
|
return nullptr;
|
|
case array_index_status::ok:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*!
|
|
@note unlike array_index(), this never throws: a malformed or unrepresentable
|
|
array index reference token is treated like a missing key (see #5395)
|
|
*/
|
|
template<typename BasicJsonType>
|
|
bool contains(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
if (!ptr->contains(reference_token))
|
|
{
|
|
// we did not find the key in the object
|
|
return false;
|
|
}
|
|
|
|
ptr = &ptr->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
return false;
|
|
}
|
|
|
|
// any parse failure (malformed index, or one that is syntactically
|
|
// valid but not representable as size_type) means the reference
|
|
// token cannot denote an existing array element -- contains() must
|
|
// not throw (see #5395), so it is treated as "not found"
|
|
typename BasicJsonType::size_type idx{};
|
|
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (idx >= ptr->size())
|
|
{
|
|
// index out of range
|
|
return false;
|
|
}
|
|
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
{
|
|
// we do not expect primitive values if there is still a
|
|
// reference token to process
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// no reference token left means we found a primitive value
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
@brief split the string input to reference tokens
|
|
|
|
@note This function is only called by the json_pointer constructor.
|
|
All exceptions below are documented there.
|
|
|
|
@throw parse_error.107 if the pointer is not empty or begins with '/'
|
|
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
|
*/
|
|
static std::vector<string_t> split(const string_t& reference_string)
|
|
{
|
|
std::vector<string_t> result;
|
|
|
|
// special case: empty reference string -> no reference tokens
|
|
if (reference_string.empty())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// check if a nonempty reference string begins with slash
|
|
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
|
{
|
|
JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
|
}
|
|
|
|
// extract the reference tokens:
|
|
// - slash: position of the last read slash (or end of string)
|
|
// - start: position after the previous slash
|
|
for (
|
|
// search for the first slash after the first character
|
|
std::size_t slash = reference_string.find_first_of('/', 1),
|
|
// set the beginning of the first reference token
|
|
start = 1;
|
|
// we can stop if start == 0 (if slash == string_t::npos)
|
|
start != 0;
|
|
// set the beginning of the next reference token
|
|
// (will eventually be 0 if slash == string_t::npos)
|
|
start = (slash == string_t::npos) ? 0 : slash + 1,
|
|
// find next slash
|
|
slash = reference_string.find_first_of('/', start))
|
|
{
|
|
// use the text between the beginning of the reference token
|
|
// (start) and the last slash (slash).
|
|
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
|
auto reference_token = string_t(reference_string.data() + start, count);
|
|
|
|
// check reference tokens are properly escaped
|
|
for (std::size_t pos = reference_token.find_first_of('~');
|
|
pos != string_t::npos;
|
|
pos = reference_token.find_first_of('~', pos + 1))
|
|
{
|
|
JSON_ASSERT(reference_token[pos] == '~');
|
|
|
|
// ~ must be followed by 0 or 1
|
|
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
|
(reference_token[pos + 1] != '0' &&
|
|
reference_token[pos + 1] != '1')))
|
|
{
|
|
JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
|
}
|
|
}
|
|
|
|
// finally, store the reference token
|
|
detail::unescape(reference_token);
|
|
result.push_back(reference_token);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@param[in] reference_string the reference string to the current value
|
|
@param[in] value the value to consider
|
|
@param[in,out] result the result object to insert values to
|
|
|
|
@note Empty objects or arrays are flattened to `null`.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static void flatten(const string_t& reference_string,
|
|
const BasicJsonType& value,
|
|
BasicJsonType& result)
|
|
{
|
|
switch (value.type())
|
|
{
|
|
case detail::value_t::array:
|
|
{
|
|
if (value.m_data.m_value.array->empty())
|
|
{
|
|
// flatten empty array as null
|
|
result[reference_string] = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// iterate array and use index as a reference string
|
|
for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)
|
|
{
|
|
flatten(detail::concat<string_t>(reference_string, '/', std::to_string(i)),
|
|
value.m_data.m_value.array->operator[](i), result);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::object:
|
|
{
|
|
if (value.m_data.m_value.object->empty())
|
|
{
|
|
// flatten empty object as null
|
|
result[reference_string] = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// iterate object and use keys as reference string
|
|
for (const auto& element : *value.m_data.m_value.object)
|
|
{
|
|
flatten(detail::concat<string_t>(reference_string, '/', detail::escape(element.first)), element.second, result);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
{
|
|
// add a primitive value with its reference string
|
|
result[reference_string] = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@param[in] value flattened JSON
|
|
|
|
@return unflattened JSON
|
|
|
|
@throw parse_error.109 if array index is not a number
|
|
@throw type_error.314 if value is not an object
|
|
@throw type_error.315 if object values are not primitive
|
|
@throw type_error.313 if value cannot be unflattened
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static BasicJsonType
|
|
unflatten(const BasicJsonType& value)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!value.is_object()))
|
|
{
|
|
JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value));
|
|
}
|
|
|
|
BasicJsonType result;
|
|
|
|
// collect the pointer prefixes that have a reference token 0 among
|
|
// their children; the values below them are arrays, all others are
|
|
// objects (see array_parents_t)
|
|
array_parents_t array_parents;
|
|
for (const auto& element : *value.m_data.m_value.object)
|
|
{
|
|
json_pointer ptr(element.first);
|
|
std::vector<string_t> prefix;
|
|
for (auto& reference_token : ptr.reference_tokens)
|
|
{
|
|
if (reference_token == "0")
|
|
{
|
|
array_parents.insert(prefix);
|
|
}
|
|
prefix.push_back(std::move(reference_token));
|
|
}
|
|
}
|
|
|
|
// iterate the JSON object values
|
|
for (const auto& element : *value.m_data.m_value.object)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
|
{
|
|
JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second));
|
|
}
|
|
|
|
// Assign the value to the reference pointed to by JSON pointer. Note
|
|
// that if the JSON pointer is "" (i.e., points to the whole value),
|
|
// function get_and_create returns a reference to the result itself.
|
|
// An assignment will then create a primitive value.
|
|
json_pointer(element.first).get_and_create(result, array_parents) = element.second;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// can't use the conversion operator because of ambiguity
|
|
json_pointer<string_t> convert() const&
|
|
{
|
|
json_pointer<string_t> result;
|
|
result.reference_tokens = reference_tokens;
|
|
return result;
|
|
}
|
|
|
|
json_pointer<string_t> convert()&&
|
|
{
|
|
json_pointer<string_t> result;
|
|
result.reference_tokens = std::move(reference_tokens);
|
|
return result;
|
|
}
|
|
|
|
public:
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
/// @brief compares two JSON pointers for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeRhs>
|
|
bool operator==(const json_pointer<RefStringTypeRhs>& rhs) const noexcept
|
|
{
|
|
return reference_tokens == rhs.reference_tokens;
|
|
}
|
|
|
|
/// @brief compares JSON pointer and string for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
|
|
bool operator==(const string_t& rhs) const
|
|
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
= delete;
|
|
#else
|
|
{
|
|
return *this == json_pointer(rhs);
|
|
}
|
|
#endif
|
|
|
|
/// @brief 3-way compares two JSON pointers
|
|
template<typename RefStringTypeRhs>
|
|
std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept // *NOPAD*
|
|
{
|
|
return reference_tokens <=> rhs.reference_tokens; // *NOPAD*
|
|
}
|
|
#else
|
|
/// @brief compares two JSON pointers for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept;
|
|
|
|
/// @brief compares JSON pointer and string for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeLhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs);
|
|
|
|
/// @brief compares string and JSON pointer for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeRhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator==(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs);
|
|
|
|
/// @brief compares two JSON pointers for inequality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept;
|
|
|
|
/// @brief compares JSON pointer and string for inequality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
|
|
template<typename RefStringTypeLhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs);
|
|
|
|
/// @brief compares string and JSON pointer for inequality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
|
|
template<typename RefStringTypeRhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator!=(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs);
|
|
|
|
/// @brief compares two JSON pointers for less-than
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_spaceship/
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept;
|
|
#endif
|
|
|
|
private:
|
|
/// the reference tokens
|
|
std::vector<string_t> reference_tokens;
|
|
};
|
|
|
|
#if !JSON_HAS_THREE_WAY_COMPARISON
|
|
// functions cannot be defined inside the class due to ODR violations
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept
|
|
{
|
|
return lhs.reference_tokens == rhs.reference_tokens;
|
|
}
|
|
|
|
template<typename RefStringTypeLhs,
|
|
typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
|
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs)
|
|
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
= delete;
|
|
#else
|
|
{
|
|
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
|
}
|
|
#endif
|
|
|
|
template<typename RefStringTypeRhs,
|
|
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
|
inline bool operator==(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs)
|
|
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
= delete;
|
|
#else
|
|
{
|
|
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
|
}
|
|
#endif
|
|
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
template<typename RefStringTypeLhs,
|
|
typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
|
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs)
|
|
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
= delete;
|
|
#else
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
#endif
|
|
|
|
template<typename RefStringTypeRhs,
|
|
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
|
inline bool operator!=(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs)
|
|
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
= delete;
|
|
#else
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
#endif
|
|
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept
|
|
{
|
|
return lhs.reference_tokens < rhs.reference_tokens;
|
|
}
|
|
#endif
|
|
|
|
namespace detail
|
|
{
|
|
/// the reference tokens of a json_pointer, for code that resolves pointers
|
|
/// without a basic_json value (such as the zero-copy view)
|
|
struct json_pointer_access
|
|
{
|
|
template<typename RefStringType>
|
|
static const std::vector<typename json_pointer<RefStringType>::string_t>& reference_tokens(const json_pointer<RefStringType>& ptr) noexcept
|
|
{
|
|
return ptr.reference_tokens;
|
|
}
|
|
};
|
|
} // namespace detail
|
|
|
|
NLOHMANN_JSON_NAMESPACE_END
|