mirror of
https://github.com/nlohmann/json.git
synced 2026-10-04 21:50:33 +00:00
* Remove unused is_sax and is_detected_convertible detail::is_sax had no user: the parser and the binary reader only use is_sax_static_asserts, so is_sax was a second, unchecked copy of the SAX event list. is_sax_static_asserts asserted boolean(bool) twice in a row, and detail::is_detected_convertible was never used anywhere. Remove all three and include <cstddef> for size_t instead of <cstdint>. Only names in nlohmann::detail are removed; behavior, public API and ABI are unchanged. The diagnostics for an incomplete SAX handler are the same, apart from the duplicated boolean() message. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Replace meta/logic.hpp with a disjunction trait meta/logic.hpp added a second set of type-level boolean helpers (cxpr_and, cxpr_or, cxpr_not, ...) next to the existing conjunction and negation in type_traits.hpp. It was used only by one static_assert in from_json_tuple_impl, two of its templates were never used, and it was the only header without the license banner and relied on transitive includes for <type_traits>. Add the missing disjunction next to conjunction and negation, use the three in the static_assert, and delete logic.hpp together with its BUILD.bazel entry. same_sign now uses disjunction as well, which resolves the 2022 TODO waiting for such a trait. The static_assert accepts and rejects the same types as before. Only names in nlohmann::detail change; behavior, public API and ABI are unchanged. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove unused would_call_std_* from NLOHMANN_CAN_CALL_STD_FUNC_IMPL Besides detail::result_of_begin/end, which is_range and iterator_t use, the macro defined a namespace detail2 with a tag type, a catch-all overload and would_call_std_begin/end, plus would_call_std_begin/end structs directly in namespace nlohmann. Nothing has used them since they were added in #3020. Reduce the macro to its detail part. Without the trailing struct the ';' after the two invocations would be an empty declaration that -Wextra-semi flags, so drop it. macro_scope.hpp included meta/detected.hpp only for this macro; all users of detected.hpp include it (or type_traits.hpp) themselves, so remove the include. Behavior and ABI are unchanged. The undocumented, untested and unused names nlohmann::would_call_std_begin, nlohmann::would_call_std_end and namespace nlohmann::detail2 are no longer declared. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Simplify is_ordered_map to reuse has_capacity is_ordered_map re-detected capacity() with a C++03 sizeof/vararg trick right after has_capacity did the same detection through is_detected. For ordered_map, the old trick took the address of std::vector::capacity, which [namespace.std]/6 makes unspecified. Reuse has_capacity instead, which removes the unspecified-behavior pointer-to-std-member and two NOLINT suppressions. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove duplicate const overload of json_pointer::get_checked The const and non-const get_checked() overloads had byte-identical 50-line bodies, differing only in the signature. The remaining template deduces a const-qualified BasicJsonType for const callers, so at(), the out_of_range::create() calls and the bounds check all still work. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix tautological clause in iter_impl's iterator category assertion The static_assert meant to check the LegacyBidirectionalIterator named requirement had a first clause comparing std::bidirectional_iterator_tag to itself, which is always true and checks nothing; only array_t::iterator was actually being checked, despite the message claiming object iterators were checked too. Drop the tautological clause, reword the message to describe what is actually checked, and note that object_t may use a forward-only iterator as long as reverse iteration and operator-- are unused. The check is intentionally not extended to object_t::iterator, since that would reject object types with forward-only iterators that compile and work correctly today. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix misplaced and stale comments in JSON_HAS_RANGES and conversions The JSON_HAS_RANGES feature-detection block had its libc++ comment sitting above the clang+libstdc++ branch it does not describe, leaving the libc++ branch uncommented and the clang+libstdc++ branch without its own rationale. Move each comment to sit under its own branch, and give the clang+libstdc++ branch (added in issue 5161) its own one-line reason referencing that issue instead of reusing the libc++ branch's comment. Also fix a duplicated-word typo ("in large in large cpp files") in from_json.hpp, drop two unanswered 2017 design questions left as comments in type_traits.hpp and from_json.hpp that no longer reflect open questions, and correct NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's @since tag from 3.12.0 to 3.13.0, the release it was actually introduced in. Part of #5708 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Support any-rank C arrays in from_json, not just rank 1-4 from_json() for C arrays had four hand-unrolled overloads (rank 1-4, added incrementally in #4262), each with its own nested loops. to_json() already handles any rank recursively, so a rank-5+ C array could be serialized but not read back with get_to()/get<>(). Replace the four overloads with one from_json() SFINAE-constrained on get<remove_all_extents<T>::type>() existing, forwarding to a pair of mutually recursive from_json_c_array_element() helpers: one assigns a non-array element via get<T>(), the other loops over a array element and recurses one dimension at a time. Each dimension still goes through at(), so type_error.304/out_of_range.401 stay unchanged; ranks 1-4 keep their existing behavior and semantics. Adds rank-5 round-trip and mismatched-shape tests to unit-conversions.cpp. Public API: additive only (rank 5+ C arrays become readable). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5708 item 1 * Move templated_json_throw into nlohmann::detail templated_json_throw() was defined in macro_scope.hpp, which is included outside NLOHMANN_JSON_NAMESPACE_BEGIN, so the helper leaked into the global namespace as ::templated_json_throw with no ABI tag. Unqualified lookup in NLOHMANN_JSON_SERIALIZE_ENUM_STRICT could then bind to a same-named function declared in the user's own namespace instead, which fails to compile with Clang ("does not name a template"). Move the helper next to the exception classes in exceptions.hpp, inside nlohmann::detail, and call it qualified as ::nlohmann::detail::templated_json_throw<...>(...) from both macro expansion sites. Rewrite the doc comment to give the real reason for the helper (JSON_THROW may expand to code that discards its argument, e.g. when exceptions are disabled) and fix the "supress" typo. templated_json_throw was never released (added by #5151 after v3.12.0), so it can be moved freely. Adds a regression test that expands NLOHMANN_JSON_SERIALIZE_ENUM_STRICT inside a namespace declaring its own templated_json_throw. Public API: no change (::templated_json_throw was an unreleased, unintentional global-namespace leak with no callers relying on its location). Overlaps #5698, which rewrites the same two macro call lines; the overlapping hunks are small and should be trivial to reconcile on rebase. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5708 item 2 * Factor the repeated JSON_HAS_RANGES/MinGW guard into one macro The std::ranges view conversion (excluded on MinGW because of its incomplete C++20 ranges support, #4916) was gated by the same #if JSON_HAS_RANGES && !defined(__MINGW32__) condition at seven independent sites in to_json.hpp and type_traits.hpp, with the MinGW rationale duplicated in two of them and missing from the rest. Since the sites come in matching pairs (one enables is_compatible_range_view and a view-based overload, the other adds the exclusion to the plain-array-type overload), a drift between any pair would produce an ambiguous or missing overload on exactly one platform. Add JSON_HAS_RANGE_VIEW_CONVERSION next to JSON_HAS_RANGES in macro_scope.hpp, combining both conditions with the #4916 reasoning in one place, #undef it in macro_unscope.hpp, and use it at all seven sites. This does not fold the MinGW check into JSON_HAS_RANGES itself: JSON_HAS_RANGES is user-overridable and also gates the enable_borrowed_range specialization in iteration_proxy.hpp, which is not excluded on MinGW. No behavior or public API change: JSON_HAS_RANGE_VIEW_CONVERSION expands to exactly the condition that was previously written out at each site. Overlaps #5585, #5600 and #3575, which touch the same to_json.hpp and type_traits.hpp lines; the change here is a mechanical search-and-replace of the guard condition and should rebase cleanly. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5708 item 11 * De-duplicate from_json.hpp's map and array-fallback bodies Several from_json() overload pairs in from_json.hpp were copies of each other, so a fix has to be applied twice (as #5681 already does): - from_json(..., std::map&) and from_json(..., std::unordered_map&) for non-string keys had identical 16-line bodies: array check, m.clear(), pair check loop, m.emplace(...). Route both through a new from_json_pair_array_to_map(j, m) helper. - The from_json_array_impl priority_tag<1> and priority_tag<0> fallbacks ran the same std::transform/std::inserter loop, differing only in ret.reserve(j.size()). Merge them into one body and, modeled on the existing from_json_object_reserve, add a from_json_array_reserve pair so the reserve() call is only made for ConstructibleArrayType that support it. Error ids (type_error.302), messages, diagnostic paths ((at(0)/at(1)) and behavior for types with/without reserve() are unchanged; only the duplication is removed. Public API: no change. Overlaps #5681, which changes the "&j" to "&p" line in both map bodies; the shared helper here should make that a one-line change instead of two on rebase. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5708 item 5 * Unify json_pointer's three array-index parsers array_index(), contains() and get_checked_or_null() each re-implemented the RFC 6901 array-index rules and the size_type range check: array_index() does the canonical parse and throws; contains() (which must not throw, #5395) re-validates every digit by hand and runs its own strtoull/ERANGE check before calling array_index() anyway, parsing every array token twice; get_checked_or_null() wraps array_index() in JSON_TRY/ JSON_INTERNAL_CATCH (detail::out_of_range&) to turn an unrepresentable index into "not found". Add a single private, noexcept parse_array_index(s, idx) returning an array_index_status (ok / leading_zero / not_a_number / unresolved / exceeds_size_type). array_index() becomes a thin wrapper mapping each status to the existing parse_error.106/109 or out_of_range.404/410; contains() and get_checked_or_null() switch on the status directly. This removes contains()'s digit-validation loop and its second strtoull call, and get_checked_or_null()'s JSON_TRY/JSON_INTERNAL_CATCH. Bugfix as a consequence: get_checked_or_null()'s JSON_TRY/ JSON_INTERNAL_CATCH was dead code under JSON_NOEXCEPTION (JSON_TRY expands to "if(true)" and the catch to "if(false)", so JSON_THROW's std::abort() ran unconditionally), meaning value() and contains() would abort instead of returning the default/false for an out-of-range-sized or oversized array index when exceptions are disabled (#5672). Switching on parse_array_index()'s return value instead of relying on an actual throw/catch fixes this: get_checked_or_null() now returns nullptr for array_index_status::unresolved/exceeds_size_type in every build configuration, and still calls JSON_THROW (aborting under JSON_NOEXCEPTION, as before) only for a malformed index (leading_zero/not_a_number), matching its documented @throw list. All existing error ids, messages and diagnostic paths are unchanged; a few reference tokens that used to fail contains()'s manual per-character validation (e.g. "1a") now fail via array_index_status::unresolved instead, with no observable difference since contains() only returns bool. Adds regression tests to unit-element_access2.cpp's "access on array type" section covering value() with an index that exceeds size_type and one with a trailing non-digit, both of which must yield the default value rather than abort/throw. Public API: no change. Overlaps #5700, #5614 and #5692, which touch the contains() and get_checked_or_null() array hunks; this change replaces those hunks with calls into the new shared parser, so a rebase will need to re-apply their token-handling changes (e.g. the empty-token case) on top of the switch statements here. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5708 item 4 * Regenerate single_include after merging develop The merge commit kept develop's single_include/nlohmann/json.hpp because make amalgamate saw it as up to date. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: switch in array_index, drop redundant inline - json_pointer::array_index() dispatches on array_index_status with a switch, matching the other parse_array_index() caller - drop `inline` from the function templates this PR adds or moves in from_json.hpp - reword a comment that described the change rather than the code Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1148 lines
43 KiB
C++
1148 lines
43 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
|
|
|
|
/// @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;
|
|
|
|
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
|
|
{
|
|
return to_string();
|
|
}
|
|
|
|
#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))
|
|
{
|
|
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))
|
|
{
|
|
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
|
|
{
|
|
return *this == json_pointer(rhs);
|
|
}
|
|
|
|
/// @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)
|
|
{
|
|
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
|
}
|
|
|
|
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)
|
|
{
|
|
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
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)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
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
|
|
|
|
NLOHMANN_JSON_NAMESPACE_END
|