mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 14:08:13 +00:00
Add element access, iteration, values, and JSON pointers to json_view (#5622)
* Add element access, iteration, values, and JSON pointers to json_view 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> * Make view lookups last-wins and chained access safe * Lookups (operator[], at, find, contains, count, value, JSON pointers) return the last member of a duplicate key, as materialize() and parse() keep it. * operator[] on a discarded view returns a discarded view instead of throwing, so v["a"]["b"] is safe for a missing "a". * operator[] and at() take any integer type (not only int and size_t), fixing ambiguous calls with unsigned, long, std::int64_t, ... * Fix the operator[] documentation, which claimed a discarded view for a type mismatch where type_error.305 is thrown. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Replace the removed operator bool of basic_json_view in the header and its tests A view is tested with is_discarded(); the lookups in at(), value() and contains(json_pointer) and the unit tests no longer rely on the explicit conversion to bool. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use named documents in the json_view tests that called root() on a temporary root() of an rvalue document is deleted, because the views would dangle. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Test discarded views with is_discarded() in the operator[] docs and examples The explicit conversion to bool of basic_json_view is gone. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Return the first member of duplicate keys from lookups again Looking up the last member of a duplicate key cannot stop at a match, so every lookup scanned the whole object (1.6 to 3.4 times slower for small objects). operator[](key), at, find, value, contains, count, and JSON pointer resolution return the first member again, as yyjson and simdjson do; materialize() and get<map>() keep the last value, as parse(). The documentation says so in the feature page and on each lookup page, and explains how to get the value parse() would give. The integer index templates and the discarded chaining of operator[] stay. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not force-inline the view's functions on MSVC __forceinline makes MSVC report C4714 for function templates it cannot inline (get<std::string>), an error under /WX. The forced inlining is only a performance hint, so use plain inline there. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make json_view.hpp pass include-what-you-use Mark the includes that are needed by design (json.hpp, the macro unscope, <tuple> for the tuple_size specialization), take size_t from <cstring>, and drop the forward declaration of basic_json_document that the friend declaration makes redundant. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Avoid GCC useless-cast warnings in the integer index tests Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make json_view.hpp pass include-what-you-use ci_single_binaries runs IWYU with --error on every header. For json_view.hpp it suggested adding <array> (std::array is used), the headers that json.hpp already provides (abi_config, abi_macros, input_adapters, json_pointer, cpp_future, string_concat, value_t, json_fwd), and <version> for std::nullptr_t, and removing <cstddef>. - include <array> - keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>) - tell IWYU not to suggest the headers that json.hpp provides: the amalgamated json_view.hpp only includes json.hpp, so including them here would duplicate their definitions Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the clang-tidy 22 findings in json_view and its tests The same changes as on the dump branch, where they were first made, so that this branch passes clang-tidy on its own. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate the amalgamated headers Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Report one-character non-numeric array indices like basic_json in the view Develop's #5800 made basic_json report a JSON pointer array index such as "/a" as parse_error.109 instead of out_of_range.404, as longer ones were; the view's array_index() follows, with "-" and the empty token still unresolved. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address the review of json_view element access From gregmarr's review of #5622: - count() counts every member with the key, so count(key) > 1 detects duplicate keys; it was contains() as 0/1 before. - get_to() passes types that get<T>() does not convert directly to from_json with the existing value, as basic_json::get_to() does, so that a from_json which sets only the fields present keeps the others. - array_index() checks the empty token and "-" first; the results are unchanged. - Docs: no "if an exception is thrown" where a view never changes anything; the borrowed source must also stay unmodified; items() on a temporary view is safe; back()/front() notes shortened; the basic_json see-also entries say how the view's results can differ. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate the amalgamated headers Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Count matching keys as unsigned in the json_view access test The conditional added 1 or 0 as int to a std::size_t, which GCC rejects with -Werror=sign-conversion (ci_test_standards_gcc). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address the second review round of json_view element access From gregmarr's review of #5622: - begin()/end(): "a null value or a discarded view iterates as an empty range", singular like the primitive case before it. - The class page's list of function groups has no trailing commas. - document_data::key_equals() compares a key node with a string, for the key comparisons that were spelled out in each lookup. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate the amalgamated headers Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Write the negated enable_if conditions of json_view with negation<> With a ! or && in the condition, astyle spaces the whole template header (template < ... >); detail::negation and a named trait keep it compact, as the library writes such conditions elsewhere. From gregmarr's review of #5630. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate the amalgamated headers Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Keep detail::negation out of json_view.hpp's enable_ifs include-what-you-use asks json_view.hpp to include detail/meta/type_traits.hpp for it, which the single header must not do (the amalgamation would duplicate json's internals). The two conditions of value() go through a trait in value.hpp, like the others. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate the amalgamated headers Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
122d718330
commit
43e97b629b
100 files changed
+5989
-71
No files matched your search
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <new> // bad_alloc, operator new, placement new
|
||||
#include <string> // string
|
||||
@@ -141,6 +141,12 @@ struct document_data
|
||||
return base[n.flags & node_flags::storage] + n.off;
|
||||
}
|
||||
|
||||
/// whether the string of node k (a key) is the n bytes at key
|
||||
bool key_equals(const node& k, const char* key, std::size_t n) const noexcept
|
||||
{
|
||||
return k.len == n && (n == 0 || std::memcmp(str(k), key, n) == 0);
|
||||
}
|
||||
|
||||
/// the node after n's subtree (containers span `next` nodes, scalars one)
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
|
||||
{
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace view
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
/// a parse error without a position (as those of json_pointer)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||
}
|
||||
|
||||
/// SAX handler of the error replay: accepts every event without storing it,
|
||||
/// and throws the exception the parser reports
|
||||
template<typename BasicJsonType>
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // ptrdiff_t, size_t
|
||||
#include <iterator> // forward_iterator_tag
|
||||
#include <string> // string, to_string
|
||||
#include <type_traits> // enable_if
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// the result of view_iterator::operator->: keeps the view alive for the
|
||||
/// duration of the member access
|
||||
template<typename View>
|
||||
class arrow_proxy
|
||||
{
|
||||
public:
|
||||
explicit arrow_proxy(const View& v) noexcept
|
||||
: m_view(v)
|
||||
{}
|
||||
|
||||
const View* operator->() const noexcept
|
||||
{
|
||||
return &m_view;
|
||||
}
|
||||
|
||||
private:
|
||||
View m_view;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief forward iterator over the elements of a basic_json_view
|
||||
|
||||
Iterates over the elements of an array or the member values of an object, in
|
||||
document order; key() gives the key of an object member. As for basic_json, a
|
||||
primitive value iterates as a range of one element (itself), and null as an
|
||||
empty range.
|
||||
*/
|
||||
template<typename View>
|
||||
class view_iterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = View;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = arrow_proxy<View>;
|
||||
using reference = View;
|
||||
using string_view_t = typename View::string_view_t;
|
||||
|
||||
view_iterator() noexcept = default;
|
||||
|
||||
/// @param[in] pos the element, or the key of the member
|
||||
/// @param[in] object whether pos is a key (its value is the next node)
|
||||
view_iterator(const document_data* d, const node* pos, bool object) noexcept
|
||||
: m_doc(d), m_pos(pos), m_value_offset(object ? 1 : 0)
|
||||
{}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept
|
||||
{
|
||||
return View(m_doc, m_pos + m_value_offset);
|
||||
}
|
||||
|
||||
pointer operator->() const noexcept
|
||||
{
|
||||
return pointer(**this);
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE view_iterator& operator++() noexcept
|
||||
{
|
||||
m_pos = document_data::after(m_pos + m_value_offset);
|
||||
return *this;
|
||||
}
|
||||
|
||||
view_iterator operator++(int) noexcept
|
||||
{
|
||||
const view_iterator r = *this;
|
||||
++*this;
|
||||
return r;
|
||||
}
|
||||
|
||||
friend bool operator==(const view_iterator& a, const view_iterator& b) noexcept
|
||||
{
|
||||
return a.m_pos == b.m_pos;
|
||||
}
|
||||
|
||||
friend bool operator!=(const view_iterator& a, const view_iterator& b) noexcept
|
||||
{
|
||||
return a.m_pos != b.m_pos;
|
||||
}
|
||||
|
||||
/// the key of the current object member; throws invalid_iterator.207 for
|
||||
/// other iterators, like basic_json's iterators
|
||||
string_view_t key() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||
{
|
||||
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
||||
}
|
||||
|
||||
View value() const noexcept
|
||||
{
|
||||
return **this;
|
||||
}
|
||||
|
||||
/// whether the iterator runs over the members of an object
|
||||
bool is_object_iterator() const noexcept
|
||||
{
|
||||
return m_value_offset != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_pos = nullptr;
|
||||
std::size_t m_value_offset = 0; ///< 1 for objects: the value follows its key
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief a (key, value) item of basic_json_view::items()
|
||||
|
||||
The key of an array element is its index, as for basic_json::items().
|
||||
Supports structured bindings: for (const auto [key, value] : view.items())
|
||||
*/
|
||||
template<typename View>
|
||||
class view_item
|
||||
{
|
||||
public:
|
||||
using string_view_t = typename View::string_view_t;
|
||||
using iterator = view_iterator<View>;
|
||||
|
||||
view_item(const iterator& it, std::size_t index)
|
||||
: m_it(it)
|
||||
{
|
||||
if (!it.is_object_iterator())
|
||||
{
|
||||
m_index = std::to_string(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// the member key, or the element index for arrays
|
||||
string_view_t key() const
|
||||
{
|
||||
if (m_it.is_object_iterator())
|
||||
{
|
||||
return m_it.key();
|
||||
}
|
||||
return string_view_t(m_index.data(), m_index.size());
|
||||
}
|
||||
|
||||
View value() const noexcept
|
||||
{
|
||||
return *m_it;
|
||||
}
|
||||
|
||||
template<std::size_t N, typename std::enable_if<N == 0, int>::type = 0>
|
||||
string_view_t get() const
|
||||
{
|
||||
return key();
|
||||
}
|
||||
|
||||
template<std::size_t N, typename std::enable_if<N == 1, int>::type = 0>
|
||||
View get() const noexcept
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
private:
|
||||
iterator m_it;
|
||||
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
/// the range returned by basic_json_view::items()
|
||||
template<typename View>
|
||||
class view_items
|
||||
{
|
||||
public:
|
||||
using item = view_item<View>;
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = item;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = item;
|
||||
|
||||
explicit iterator(const view_iterator<View>& it) noexcept
|
||||
: m_it(it)
|
||||
{}
|
||||
|
||||
item operator*() const
|
||||
{
|
||||
return item(m_it, m_index);
|
||||
}
|
||||
|
||||
iterator& operator++() noexcept
|
||||
{
|
||||
++m_it;
|
||||
++m_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) noexcept
|
||||
{
|
||||
const iterator r = *this;
|
||||
++*this;
|
||||
return r;
|
||||
}
|
||||
|
||||
friend bool operator==(const iterator& a, const iterator& b) noexcept
|
||||
{
|
||||
return a.m_it == b.m_it;
|
||||
}
|
||||
|
||||
friend bool operator!=(const iterator& a, const iterator& b) noexcept
|
||||
{
|
||||
return a.m_it != b.m_it;
|
||||
}
|
||||
|
||||
private:
|
||||
view_iterator<View> m_it;
|
||||
std::size_t m_index = 0;
|
||||
};
|
||||
|
||||
explicit view_items(const View& v) noexcept
|
||||
: m_view(v)
|
||||
{}
|
||||
|
||||
iterator begin() const noexcept
|
||||
{
|
||||
return iterator(m_view.begin());
|
||||
}
|
||||
|
||||
iterator end() const noexcept
|
||||
{
|
||||
return iterator(m_view.end());
|
||||
}
|
||||
|
||||
private:
|
||||
View m_view;
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -0,0 +1,176 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint16_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <type_traits> // integral_constant, is_integral, is_same
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// equality test for strings of one length n <= 16: two overlapping loads per
|
||||
/// string (the first and the last 8, 4, or 2 bytes) replace a memcmp, and no
|
||||
/// byte outside [s, s + n) is read
|
||||
class short_key
|
||||
{
|
||||
public:
|
||||
short_key(const unsigned char* k, std::size_t n) noexcept
|
||||
: m_n(n)
|
||||
{
|
||||
load(k, m_a, m_b);
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool matches(const unsigned char* s) const noexcept
|
||||
{
|
||||
std::uint64_t a = 0;
|
||||
std::uint64_t b = 0;
|
||||
load(s, a, b);
|
||||
return a == m_a && b == m_b;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t load_word(const unsigned char* s) noexcept
|
||||
{
|
||||
T w = 0;
|
||||
std::memcpy(&w, s, sizeof(T));
|
||||
return w;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void load(const unsigned char* s, std::uint64_t& a, std::uint64_t& b) const noexcept
|
||||
{
|
||||
if (m_n >= 8)
|
||||
{
|
||||
a = load_word<std::uint64_t>(s);
|
||||
b = load_word<std::uint64_t>(s + m_n - 8);
|
||||
}
|
||||
else if (m_n >= 4)
|
||||
{
|
||||
a = load_word<std::uint32_t>(s);
|
||||
b = load_word<std::uint32_t>(s + m_n - 4);
|
||||
}
|
||||
else if (m_n >= 2)
|
||||
{
|
||||
a = load_word<std::uint16_t>(s);
|
||||
b = load_word<std::uint16_t>(s + m_n - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
a = m_n == 1 ? s[0] : 0;
|
||||
b = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t m_n;
|
||||
std::uint64_t m_a = 0;
|
||||
std::uint64_t m_b = 0;
|
||||
};
|
||||
|
||||
/// the key node of the first member of an object with the given key, or
|
||||
/// nullptr (the search stops at the first match; materialize() and parse()
|
||||
/// keep the last value of a duplicate key instead); most keys are rejected by
|
||||
/// their length, from the index alone
|
||||
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
{
|
||||
const node* const end = document_data::child_end(object);
|
||||
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
if (NLOHMANN_VIEW_LIKELY(n <= 16))
|
||||
{
|
||||
const short_key probe(k, n);
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (d.key_equals(*m, key, n))
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// the number of members of an object with this key (duplicates included)
|
||||
inline std::size_t count_members(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
{
|
||||
std::size_t result = 0;
|
||||
const node* const end = document_data::child_end(object);
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (d.key_equals(*m, key, n))
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// whether an integer type is accepted as an array index by the view's
|
||||
/// operator[] and at(): every integer type but bool and size_t, which has its
|
||||
/// own overload
|
||||
template<typename T>
|
||||
struct is_index_type : std::integral_constant < bool,
|
||||
std::is_integral<T>::value && !std::is_same<T, bool>::value && !std::is_same<T, std::size_t>::value >
|
||||
{};
|
||||
|
||||
/// an integer as an index: negative values, and values that do not fit a
|
||||
/// size_t, map to the largest size_t (out of range for every array)
|
||||
template<typename SizeType, typename IntegerType>
|
||||
SizeType to_index(IntegerType idx) noexcept
|
||||
{
|
||||
const IntegerType zero = 0;
|
||||
const auto result = static_cast<SizeType>(idx); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): negative values are mapped below
|
||||
return (idx < zero || static_cast<IntegerType>(result) != idx) ? (std::numeric_limits<SizeType>::max)() : result;
|
||||
}
|
||||
|
||||
/// the element of an array at an index below its size
|
||||
inline const node* element_at(const node* array, std::size_t idx) noexcept
|
||||
{
|
||||
const node* e = document_data::first_child(array);
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
e = document_data::after(e);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/// the last element of a non-empty array, or the key of the last member of a
|
||||
/// non-empty object
|
||||
inline const node* last_child(const node* container) noexcept
|
||||
{
|
||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||
const node* const end = document_data::child_end(container);
|
||||
const node* last = document_data::first_child(container);
|
||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||
{
|
||||
last = c;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -28,7 +28,10 @@
|
||||
#elif defined(_MSC_VER)
|
||||
#define NLOHMANN_VIEW_LIKELY(x) (x)
|
||||
#define NLOHMANN_VIEW_UNLIKELY(x) (x)
|
||||
#define NLOHMANN_VIEW_ALWAYS_INLINE __forceinline
|
||||
// plain inline: __forceinline makes MSVC report C4714 (not inlined) for
|
||||
// function templates it cannot inline, which is an error under /WX; the
|
||||
// forced inlining is only a performance hint
|
||||
#define NLOHMANN_VIEW_ALWAYS_INLINE inline
|
||||
#define NLOHMANN_VIEW_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define NLOHMANN_VIEW_LIKELY(x) (x)
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// what resolving a JSON pointer does where it cannot continue
|
||||
enum class pointer_mode
|
||||
{
|
||||
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
|
||||
checked, ///< as basic_json::at(): out_of_range.401/403
|
||||
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
|
||||
contains, ///< as basic_json::contains(): no exceptions at all
|
||||
};
|
||||
|
||||
/// the outcome of reading an array index from a reference token
|
||||
enum class index_status
|
||||
{
|
||||
ok,
|
||||
leading_zero, ///< parse_error.106
|
||||
not_number, ///< parse_error.109
|
||||
unresolved, ///< out_of_range.404
|
||||
too_large, ///< out_of_range.410
|
||||
};
|
||||
|
||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||
/// but reports errors instead of throwing them
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
// as basic_json: the empty token and "-" (past the last element) are
|
||||
// unresolved rather than not a number
|
||||
if (s.empty() || (s.size() == 1 && s[0] == '-'))
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
if (s[0] < '0' || s[0] > '9')
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (const auto c : s)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
}
|
||||
v = (v * 10) + d;
|
||||
}
|
||||
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
|
||||
{
|
||||
return index_status::too_large;
|
||||
}
|
||||
idx = static_cast<std::size_t>(v);
|
||||
return index_status::ok;
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
template<typename StringType>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case index_status::leading_zero:
|
||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_number:
|
||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
||||
case index_status::too_large:
|
||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
case index_status::unresolved:
|
||||
case index_status::ok:
|
||||
default:
|
||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief resolve the reference tokens of a JSON pointer, starting at a view
|
||||
|
||||
The exceptions are those basic_json throws for the same pointer; where
|
||||
basic_json's behavior is undefined (a missing key or an index out of range
|
||||
with const operator[]), the result is a discarded view.
|
||||
*/
|
||||
template<typename View, typename Tokens>
|
||||
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
using string_view_t = typename View::string_view_t;
|
||||
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
if (cur.is_object())
|
||||
{
|
||||
const auto it = cur.find(string_view_t(token.data(), token.size()));
|
||||
if (it == cur.end())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = *it;
|
||||
}
|
||||
else if (cur.is_array())
|
||||
{
|
||||
if (token.size() == 1 && token[0] == '-')
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
std::size_t idx = 0;
|
||||
const index_status status = array_index(token, idx);
|
||||
if (status != index_status::ok)
|
||||
{
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||
{
|
||||
throw_array_index_error(status, token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
if (idx >= cur.size())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = cur[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -0,0 +1,140 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // nullptr_t, size_t
|
||||
#include <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // decay, enable_if, is_arithmetic, is_constructible, is_integral, is_same
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/number.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// selects a conversion by its target type
|
||||
template<typename T>
|
||||
struct value_tag {};
|
||||
|
||||
/*!
|
||||
@brief the number or boolean of a node converted to an arithmetic type
|
||||
|
||||
As basic_json's get<T>() for arithmetic types: integers and floats are
|
||||
converted with static_cast, booleans give 0 or 1, and other types throw
|
||||
type_error.302.
|
||||
*/
|
||||
template<typename T, typename BasicJsonType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
|
||||
{
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
return static_cast<T>(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(n)));
|
||||
case value_t::number_integer:
|
||||
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
|
||||
case value_t::number_float:
|
||||
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d.str(n), n));
|
||||
case value_t::boolean:
|
||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
}
|
||||
|
||||
/// std::vector from an array, element by element (type_error.302 otherwise)
|
||||
template<typename View, typename U, typename A>
|
||||
std::vector<U, A> vector_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||
{
|
||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
||||
}
|
||||
std::vector<U, A> r;
|
||||
r.reserve(v.size());
|
||||
for (const View e : v)
|
||||
{
|
||||
r.push_back(e.template get<U>());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// a map with string keys from an object; with duplicate keys, the last
|
||||
/// value is kept, as parse() does (type_error.302 for other types)
|
||||
template<typename Map, typename View>
|
||||
Map map_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||
{
|
||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
||||
}
|
||||
Map r;
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
const auto key = it.key();
|
||||
r[typename Map::key_type(key.data(), key.size())] = it.value().template get<typename Map::mapped_type>();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// whether a map type is read member by member (its keys are made from
|
||||
/// characters and a length); other maps go through basic_json
|
||||
template<typename Key>
|
||||
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
|
||||
|
||||
/// the arithmetic types arithmetic_value() converts to (bool has its own
|
||||
/// conversion)
|
||||
template<typename T>
|
||||
struct is_non_bool_arithmetic : conjunction<std::is_arithmetic<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// the default values of value() other than C strings, which have their own
|
||||
/// overload (returning string_t)
|
||||
template<typename T>
|
||||
struct is_non_c_string_default : negation<std::is_same<typename std::decay<T>::type, const char*>> {};
|
||||
|
||||
/// whether View::get<T>() converts to T without materialize(); get_to()
|
||||
/// assigns these, and passes the others to from_json
|
||||
template<typename T, typename BasicJsonType, typename View>
|
||||
struct is_direct_value : std::integral_constant < bool,
|
||||
std::is_arithmetic<T>::value || std::is_same<T, std::nullptr_t>::value
|
||||
|| std::is_same<T, typename View::string_view_t>::value
|
||||
|| std::is_same<T, BasicJsonType>::value || std::is_same<T, View>::value >
|
||||
{};
|
||||
|
||||
template<typename Traits, typename Alloc, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::basic_string<char, Traits, Alloc>, BasicJsonType, View> : std::true_type {};
|
||||
|
||||
template<typename U, typename A, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::vector<U, A>, BasicJsonType, View> : std::true_type {};
|
||||
|
||||
template<typename K, typename V, typename C, typename A, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::map<K, V, C, A>, BasicJsonType, View> : is_string_key<K> {};
|
||||
|
||||
template<typename K, typename V, typename H, typename E, typename A, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::unordered_map<K, V, H, E, A>, BasicJsonType, View> : is_string_key<K> {};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
Reference in new issue
Block a user