mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 14:05:18 +00:00
Add values and JSON pointers to json_view
basic_json_view gains get<T>(), get_to(), value() with keys and JSON pointers, and operator[], at(), and contains() with JSON pointers, plus two functions basic_json has no counterpart for: - get_string(): the string without a copy (a string_view into the source, or into the decoded strings for strings with escapes) - number_token(): the text of a number as it appears in the source get<T>() converts arithmetic types, strings (also string_view_t), std::nullptr_t, std::vector, maps with string keys, and views directly; floats are converted from the digit layout recorded by the parser with the library's conversion chain, so the values are bit-identical to parse(). Other types, including user types with from_json(), go through materialize(). The exceptions are those of basic_json, message included. Where const basic_json has undefined behavior (a missing key or an index out of range with operator[] and a JSON pointer), the result is a discarded view; value() returns the default wherever basic_json catches out_of_range, and contains() never throws. Array indices of JSON pointers follow json_pointer's rules (parse_error.106/109, out_of_range.404/410). Tests compare the conversions of 2,000 generated documents, 20,000 float tokens (double and float, bit for bit), and every JSON pointer of 1,000 documents with basic_json, and the exceptions for malformed pointers. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -77,8 +77,10 @@ cc_library(
|
||||
"include/nlohmann/detail/view/materialize.hpp",
|
||||
"include/nlohmann/detail/view/node.hpp",
|
||||
"include/nlohmann/detail/view/number.hpp",
|
||||
"include/nlohmann/detail/view/pointer.hpp",
|
||||
"include/nlohmann/detail/view/scan.hpp",
|
||||
"include/nlohmann/detail/view/string_ref.hpp",
|
||||
"include/nlohmann/detail/view/value.hpp",
|
||||
"include/nlohmann/json.hpp",
|
||||
"include/nlohmann/json_fwd.hpp",
|
||||
"include/nlohmann/json_view.hpp",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throw the exception BasicJsonType::parse would throw for this input
|
||||
|
||||
|
||||
171
include/nlohmann/detail/view/pointer.hpp
Normal file
171
include/nlohmann/detail/view/pointer.hpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// what resolving a JSON pointer does where it cannot continue
|
||||
enum class pointer_mode
|
||||
{
|
||||
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
|
||||
checked, ///< as basic_json::at(): out_of_range.401/403
|
||||
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
|
||||
contains, ///< as basic_json::contains(): no exceptions at all
|
||||
};
|
||||
|
||||
/// the outcome of reading an array index from a reference token
|
||||
enum class index_status
|
||||
{
|
||||
ok,
|
||||
leading_zero, ///< parse_error.106
|
||||
not_number, ///< parse_error.109
|
||||
unresolved, ///< out_of_range.404
|
||||
too_large, ///< out_of_range.410
|
||||
};
|
||||
|
||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||
/// but reports errors instead of throwing them
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.empty())
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
}
|
||||
v = (v * 10) + d;
|
||||
}
|
||||
if (v >= static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)()))
|
||||
{
|
||||
return index_status::too_large;
|
||||
}
|
||||
idx = static_cast<std::size_t>(v);
|
||||
return index_status::ok;
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
template<typename StringType>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case index_status::leading_zero:
|
||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_number:
|
||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
||||
case index_status::too_large:
|
||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
case index_status::unresolved:
|
||||
case index_status::ok:
|
||||
default:
|
||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief resolve the reference tokens of a JSON pointer, starting at a view
|
||||
|
||||
The exceptions are those basic_json throws for the same pointer; where
|
||||
basic_json's behavior is undefined (a missing key or an index out of range
|
||||
with const operator[]), the result is a discarded view.
|
||||
*/
|
||||
template<typename View, typename Tokens>
|
||||
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
using string_view_t = typename View::string_view_t;
|
||||
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
if (cur.is_object())
|
||||
{
|
||||
const auto it = cur.find(string_view_t(token.data(), token.size()));
|
||||
if (it == cur.end())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = *it;
|
||||
}
|
||||
else if (cur.is_array())
|
||||
{
|
||||
if (token.size() == 1 && token[0] == '-')
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
std::size_t idx = 0;
|
||||
const index_status status = array_index(token, idx);
|
||||
if (status != index_status::ok)
|
||||
{
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||
{
|
||||
throw_array_index_error(status, token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
if (idx >= cur.size())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = cur[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
108
include/nlohmann/detail/view/value.hpp
Normal file
108
include/nlohmann/detail/view/value.hpp
Normal file
@@ -0,0 +1,108 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // enable_if, is_constructible
|
||||
#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>
|
||||
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> {};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -27,11 +27,14 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <string> // string
|
||||
#include <tuple> // tuple_element, tuple_size
|
||||
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <utility> // forward, move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -49,7 +52,9 @@
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/materialize.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/pointer.hpp>
|
||||
#include <nlohmann/detail/view/string_ref.hpp>
|
||||
#include <nlohmann/detail/view/value.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
@@ -268,6 +273,14 @@ class basic_json_view
|
||||
return operator[](static_cast<size_type>(idx));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to; a discarded view if a key is
|
||||
/// missing or an index is out of range. Other errors throw what const
|
||||
/// basic_json::operator[] throws.
|
||||
basic_json_view operator[](const json_pointer& ptr) const
|
||||
{
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
|
||||
}
|
||||
|
||||
/// the value of the member with this key (the first one, should the key
|
||||
/// occur more than once). Throws type_error.304 if this is not an object,
|
||||
/// and out_of_range.403 if there is no such member.
|
||||
@@ -315,6 +328,51 @@ class basic_json_view
|
||||
return at(static_cast<size_type>(idx));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to; throws what basic_json::at()
|
||||
/// throws if it cannot be resolved
|
||||
basic_json_view at(const json_pointer& ptr) const
|
||||
{
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked);
|
||||
}
|
||||
|
||||
/// the member with this key converted to T, or the default value if there
|
||||
/// is no such member (the first one, should the key occur more than
|
||||
/// once). Throws type_error.306 if this is not an object.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(string_view_t key, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
return r ? r.template get<T>() : default_value;
|
||||
}
|
||||
|
||||
string_t value(string_view_t key, const char* default_value) const
|
||||
{
|
||||
return value(key, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
/// value if the pointer cannot be resolved. Throws type_error.306 if this
|
||||
/// is neither an object nor an array.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(const json_pointer& ptr, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r ? r.template get<T>() : default_value;
|
||||
}
|
||||
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||
{
|
||||
return value(ptr, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the first element or member value; a primitive value itself. Throws
|
||||
/// invalid_iterator.214 for null, discarded views, and empty containers.
|
||||
basic_json_view front() const
|
||||
@@ -381,6 +439,13 @@ class basic_json_view
|
||||
return contains(string_view_t(key.data(), key.size()));
|
||||
}
|
||||
|
||||
/// whether a JSON pointer can be resolved (never throws, as
|
||||
/// basic_json::contains())
|
||||
bool contains(const json_pointer& ptr) const
|
||||
{
|
||||
return static_cast<bool>(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains));
|
||||
}
|
||||
|
||||
/// 1 if this is an object with a member with this key, else 0 (duplicate
|
||||
/// keys count once)
|
||||
size_type count(string_view_t key) const
|
||||
@@ -438,6 +503,50 @@ class basic_json_view
|
||||
return detail::view::view_items<basic_json_view>(*this);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// conversion //
|
||||
////////////////
|
||||
|
||||
/// the value converted to T, as BasicJsonType::get<T>(): arithmetic types,
|
||||
/// strings (string_view_t without a copy), std::nullptr_t, std::vector,
|
||||
/// maps with string keys, and views are converted directly; other types
|
||||
/// through materialize().get<T>()
|
||||
template<typename T>
|
||||
T get() const
|
||||
{
|
||||
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& get_to(T& v) const
|
||||
{
|
||||
v = get<T>();
|
||||
return v;
|
||||
}
|
||||
|
||||
/// the string, without a copy; valid as long as the view is. Throws
|
||||
/// type_error.302 for other types.
|
||||
string_view_t get_string() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_node), m_node->len);
|
||||
}
|
||||
|
||||
/// the text of a number as it appears in the source (e.g. "1.50", "1E2",
|
||||
/// or an integer with more digits than any number type holds). Throws
|
||||
/// type_error.302 for other types.
|
||||
string_view_t number_token() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_node), detail::view::number_length(*m_node));
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// materialize //
|
||||
/////////////////
|
||||
@@ -478,6 +587,83 @@ class basic_json_view
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
|
||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
||||
}
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
|
||||
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
|
||||
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
}
|
||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||
}
|
||||
|
||||
std::nullptr_t get_impl(detail::view::value_tag<std::nullptr_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
string_view_t get_impl(detail::view::value_tag<string_view_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return get_string();
|
||||
}
|
||||
|
||||
template<typename Traits, typename Alloc>
|
||||
std::basic_string<char, Traits, Alloc> get_impl(detail::view::value_tag<std::basic_string<char, Traits, Alloc>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
const string_view_t s = get_string();
|
||||
return std::basic_string<char, Traits, Alloc>(s.data(), s.size());
|
||||
}
|
||||
|
||||
BasicJsonType get_impl(detail::view::value_tag<BasicJsonType> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return materialize();
|
||||
}
|
||||
|
||||
basic_json_view get_impl(detail::view::value_tag<basic_json_view> /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, typename A>
|
||||
std::vector<U, A> get_impl(detail::view::value_tag<std::vector<U, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::vector_value<basic_json_view, U, A>(*this);
|
||||
}
|
||||
|
||||
template<typename K, typename V, typename C, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
||||
std::map<K, V, C, A> get_impl(detail::view::value_tag<std::map<K, V, C, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::map_value<std::map<K, V, C, A>>(*this);
|
||||
}
|
||||
|
||||
template<typename K, typename V, typename H, typename E, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
||||
std::unordered_map<K, V, H, E, A> get_impl(detail::view::value_tag<std::unordered_map<K, V, H, E, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::map_value<std::unordered_map<K, V, H, E, A>>(*this);
|
||||
}
|
||||
|
||||
/// everything else through the BasicJsonType value (from_json included)
|
||||
template<typename T>
|
||||
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<0> /*unused*/) const
|
||||
{
|
||||
return materialize().template get<T>();
|
||||
}
|
||||
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_node = nullptr;
|
||||
};
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <string> // string
|
||||
#include <tuple> // tuple_element, tuple_size
|
||||
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <utility> // forward, move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -1621,6 +1624,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));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throw the exception BasicJsonType::parse would throw for this input
|
||||
|
||||
@@ -2379,6 +2388,181 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
// #include <nlohmann/detail/view/pointer.hpp>
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
|
||||
// #include <nlohmann/json.hpp>
|
||||
// #include <nlohmann/detail/view/errors.hpp>
|
||||
|
||||
// #include <nlohmann/detail/view/macro_scope.hpp>
|
||||
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// what resolving a JSON pointer does where it cannot continue
|
||||
enum class pointer_mode
|
||||
{
|
||||
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
|
||||
checked, ///< as basic_json::at(): out_of_range.401/403
|
||||
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
|
||||
contains, ///< as basic_json::contains(): no exceptions at all
|
||||
};
|
||||
|
||||
/// the outcome of reading an array index from a reference token
|
||||
enum class index_status
|
||||
{
|
||||
ok,
|
||||
leading_zero, ///< parse_error.106
|
||||
not_number, ///< parse_error.109
|
||||
unresolved, ///< out_of_range.404
|
||||
too_large, ///< out_of_range.410
|
||||
};
|
||||
|
||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||
/// but reports errors instead of throwing them
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.empty())
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
}
|
||||
v = (v * 10) + d;
|
||||
}
|
||||
if (v >= static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)()))
|
||||
{
|
||||
return index_status::too_large;
|
||||
}
|
||||
idx = static_cast<std::size_t>(v);
|
||||
return index_status::ok;
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
template<typename StringType>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case index_status::leading_zero:
|
||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_number:
|
||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
||||
case index_status::too_large:
|
||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
case index_status::unresolved:
|
||||
case index_status::ok:
|
||||
default:
|
||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief resolve the reference tokens of a JSON pointer, starting at a view
|
||||
|
||||
The exceptions are those basic_json throws for the same pointer; where
|
||||
basic_json's behavior is undefined (a missing key or an index out of range
|
||||
with const operator[]), the result is a discarded view.
|
||||
*/
|
||||
template<typename View, typename Tokens>
|
||||
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
using string_view_t = typename View::string_view_t;
|
||||
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
if (cur.is_object())
|
||||
{
|
||||
const auto it = cur.find(string_view_t(token.data(), token.size()));
|
||||
if (it == cur.end())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = *it;
|
||||
}
|
||||
else if (cur.is_array())
|
||||
{
|
||||
if (token.size() == 1 && token[0] == '-')
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
std::size_t idx = 0;
|
||||
const index_status status = array_index(token, idx);
|
||||
if (status != index_status::ok)
|
||||
{
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||
{
|
||||
throw_array_index_error(status, token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
if (idx >= cur.size())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = cur[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/view/string_ref.hpp>
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
@@ -2493,6 +2677,121 @@ class string_ref
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/view/value.hpp>
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // enable_if, is_constructible
|
||||
#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>
|
||||
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> {};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
@@ -2711,6 +3010,14 @@ class basic_json_view
|
||||
return operator[](static_cast<size_type>(idx));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to; a discarded view if a key is
|
||||
/// missing or an index is out of range. Other errors throw what const
|
||||
/// basic_json::operator[] throws.
|
||||
basic_json_view operator[](const json_pointer& ptr) const
|
||||
{
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
|
||||
}
|
||||
|
||||
/// the value of the member with this key (the first one, should the key
|
||||
/// occur more than once). Throws type_error.304 if this is not an object,
|
||||
/// and out_of_range.403 if there is no such member.
|
||||
@@ -2758,6 +3065,51 @@ class basic_json_view
|
||||
return at(static_cast<size_type>(idx));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to; throws what basic_json::at()
|
||||
/// throws if it cannot be resolved
|
||||
basic_json_view at(const json_pointer& ptr) const
|
||||
{
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked);
|
||||
}
|
||||
|
||||
/// the member with this key converted to T, or the default value if there
|
||||
/// is no such member (the first one, should the key occur more than
|
||||
/// once). Throws type_error.306 if this is not an object.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(string_view_t key, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
return r ? r.template get<T>() : default_value;
|
||||
}
|
||||
|
||||
string_t value(string_view_t key, const char* default_value) const
|
||||
{
|
||||
return value(key, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
/// value if the pointer cannot be resolved. Throws type_error.306 if this
|
||||
/// is neither an object nor an array.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(const json_pointer& ptr, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r ? r.template get<T>() : default_value;
|
||||
}
|
||||
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||
{
|
||||
return value(ptr, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the first element or member value; a primitive value itself. Throws
|
||||
/// invalid_iterator.214 for null, discarded views, and empty containers.
|
||||
basic_json_view front() const
|
||||
@@ -2824,6 +3176,13 @@ class basic_json_view
|
||||
return contains(string_view_t(key.data(), key.size()));
|
||||
}
|
||||
|
||||
/// whether a JSON pointer can be resolved (never throws, as
|
||||
/// basic_json::contains())
|
||||
bool contains(const json_pointer& ptr) const
|
||||
{
|
||||
return static_cast<bool>(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains));
|
||||
}
|
||||
|
||||
/// 1 if this is an object with a member with this key, else 0 (duplicate
|
||||
/// keys count once)
|
||||
size_type count(string_view_t key) const
|
||||
@@ -2881,6 +3240,50 @@ class basic_json_view
|
||||
return detail::view::view_items<basic_json_view>(*this);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// conversion //
|
||||
////////////////
|
||||
|
||||
/// the value converted to T, as BasicJsonType::get<T>(): arithmetic types,
|
||||
/// strings (string_view_t without a copy), std::nullptr_t, std::vector,
|
||||
/// maps with string keys, and views are converted directly; other types
|
||||
/// through materialize().get<T>()
|
||||
template<typename T>
|
||||
T get() const
|
||||
{
|
||||
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& get_to(T& v) const
|
||||
{
|
||||
v = get<T>();
|
||||
return v;
|
||||
}
|
||||
|
||||
/// the string, without a copy; valid as long as the view is. Throws
|
||||
/// type_error.302 for other types.
|
||||
string_view_t get_string() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_node), m_node->len);
|
||||
}
|
||||
|
||||
/// the text of a number as it appears in the source (e.g. "1.50", "1E2",
|
||||
/// or an integer with more digits than any number type holds). Throws
|
||||
/// type_error.302 for other types.
|
||||
string_view_t number_token() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_node), detail::view::number_length(*m_node));
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// materialize //
|
||||
/////////////////
|
||||
@@ -2921,6 +3324,83 @@ class basic_json_view
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
|
||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
||||
}
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
|
||||
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
|
||||
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
}
|
||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||
}
|
||||
|
||||
std::nullptr_t get_impl(detail::view::value_tag<std::nullptr_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
string_view_t get_impl(detail::view::value_tag<string_view_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return get_string();
|
||||
}
|
||||
|
||||
template<typename Traits, typename Alloc>
|
||||
std::basic_string<char, Traits, Alloc> get_impl(detail::view::value_tag<std::basic_string<char, Traits, Alloc>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
const string_view_t s = get_string();
|
||||
return std::basic_string<char, Traits, Alloc>(s.data(), s.size());
|
||||
}
|
||||
|
||||
BasicJsonType get_impl(detail::view::value_tag<BasicJsonType> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return materialize();
|
||||
}
|
||||
|
||||
basic_json_view get_impl(detail::view::value_tag<basic_json_view> /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, typename A>
|
||||
std::vector<U, A> get_impl(detail::view::value_tag<std::vector<U, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::vector_value<basic_json_view, U, A>(*this);
|
||||
}
|
||||
|
||||
template<typename K, typename V, typename C, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
||||
std::map<K, V, C, A> get_impl(detail::view::value_tag<std::map<K, V, C, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::map_value<std::map<K, V, C, A>>(*this);
|
||||
}
|
||||
|
||||
template<typename K, typename V, typename H, typename E, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
||||
std::unordered_map<K, V, H, E, A> get_impl(detail::view::value_tag<std::unordered_map<K, V, H, E, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::map_value<std::unordered_map<K, V, H, E, A>>(*this);
|
||||
}
|
||||
|
||||
/// everything else through the BasicJsonType value (from_json included)
|
||||
template<typename T>
|
||||
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<0> /*unused*/) const
|
||||
{
|
||||
return materialize().template get<T>();
|
||||
}
|
||||
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_node = nullptr;
|
||||
};
|
||||
|
||||
@@ -17,13 +17,18 @@ using nlohmann::ordered_json_document;
|
||||
using nlohmann::ordered_json_view;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -693,3 +698,368 @@ TEST_CASE("json_view element access and iteration")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// an exception message without the context that basic_json adds with
|
||||
// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) ");
|
||||
// the view's exceptions have no such context
|
||||
std::string without_path(std::string msg)
|
||||
{
|
||||
for (const char* prefix :
|
||||
{"] (/", "] (bytes "
|
||||
})
|
||||
{
|
||||
const std::size_t open = msg.find(prefix);
|
||||
if (open != std::string::npos)
|
||||
{
|
||||
msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2));
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool has_duplicate_keys(const ordered_json_view& v)
|
||||
{
|
||||
if (v.is_object() && v.size() != v.materialize().size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const ordered_json_view e : v)
|
||||
{
|
||||
if (e.is_structured() && has_duplicate_keys(e))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// compares the conversions of a view with those of ordered_json
|
||||
void check_values(const ordered_json_view& v, const ordered_json& j, const std::string& text)
|
||||
{
|
||||
CHECK(v.get<ordered_json>() == j);
|
||||
switch (j.type())
|
||||
{
|
||||
case json::value_t::number_integer:
|
||||
case json::value_t::number_unsigned:
|
||||
case json::value_t::number_float:
|
||||
{
|
||||
// (converting a float out of range of the target type is undefined)
|
||||
if (j.is_number_unsigned())
|
||||
{
|
||||
CHECK(v.get<std::uint64_t>() == j.get<std::uint64_t>());
|
||||
}
|
||||
else if (j.is_number_integer())
|
||||
{
|
||||
CHECK(v.get<std::int64_t>() == j.get<std::int64_t>());
|
||||
}
|
||||
const double a = v.get<double>();
|
||||
const double b = j.get<double>();
|
||||
CHECK(std::memcmp(&a, &b, sizeof(double)) == 0);
|
||||
if (std::abs(b) < 1e9)
|
||||
{
|
||||
CHECK(v.get<int>() == j.get<int>());
|
||||
}
|
||||
const auto token = v.number_token();
|
||||
CHECK(text.compare(v.source_offset(), token.size(), token.data(), token.size()) == 0);
|
||||
break;
|
||||
}
|
||||
case json::value_t::string:
|
||||
CHECK(v.get<std::string>() == j.get<std::string>());
|
||||
CHECK(std::string(v.get_string().data(), v.get_string().size()) == j.get<std::string>());
|
||||
break;
|
||||
case json::value_t::boolean:
|
||||
CHECK(v.get<bool>() == j.get<bool>());
|
||||
CHECK(v.get<int>() == j.get<int>());
|
||||
break;
|
||||
case json::value_t::null:
|
||||
CHECK(v.get<std::nullptr_t>() == nullptr);
|
||||
break;
|
||||
case json::value_t::array:
|
||||
CHECK(v.get<std::vector<ordered_json>>() == j.get<std::vector<ordered_json>>());
|
||||
break;
|
||||
case json::value_t::object:
|
||||
CHECK((v.get<std::map<std::string, ordered_json>>() == j.get<std::map<std::string, ordered_json>>()));
|
||||
break;
|
||||
case json::value_t::binary:
|
||||
case json::value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// conversions to the wrong type throw what basic_json throws
|
||||
if (!j.is_number())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<int>()); })));
|
||||
}
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<bool>()); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::string>()); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); })));
|
||||
if (!j.is_array())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); })));
|
||||
}
|
||||
if (!j.is_object())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); })));
|
||||
}
|
||||
|
||||
if (v.is_array())
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (const ordered_json_view e : v)
|
||||
{
|
||||
check_values(e, j[i++], text);
|
||||
}
|
||||
}
|
||||
else if (v.is_object())
|
||||
{
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
const std::string key(it.key().data(), it.key().size());
|
||||
if (v.size() == j.size()) // (no duplicate keys)
|
||||
{
|
||||
check_values(it.value(), j[key], text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct record
|
||||
{
|
||||
std::string name{};
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
void from_json(const json& j, record& r)
|
||||
{
|
||||
j.at("name").get_to(r.name);
|
||||
j.at("count").get_to(r.count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("json_view values")
|
||||
{
|
||||
SECTION("generated documents")
|
||||
{
|
||||
generator g;
|
||||
for (int i = 0; i < 2000; ++i)
|
||||
{
|
||||
std::string text;
|
||||
g.value(text, 0);
|
||||
CAPTURE(text);
|
||||
const ordered_json_document d = ordered_json_document::parse(text);
|
||||
check_values(d.root(), ordered_json::parse(text), text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("floats are converted as parse() converts them")
|
||||
{
|
||||
std::mt19937_64 rng(5295);
|
||||
std::vector<std::string> tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324",
|
||||
"0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890",
|
||||
"9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16"
|
||||
};
|
||||
for (int i = 0; i < 20000; ++i)
|
||||
{
|
||||
const std::uint64_t bits = rng();
|
||||
double d = 0;
|
||||
std::memcpy(&d, &bits, sizeof(d));
|
||||
if (!std::isfinite(d))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::array<char, 400> buf{};
|
||||
switch (i % 5) // NOLINT(hicpp-multiway-paths-covered)
|
||||
{
|
||||
case 0:
|
||||
std::snprintf(buf.data(), buf.size(), "%.17g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
case 1:
|
||||
std::snprintf(buf.data(), buf.size(), "%.15g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
case 2:
|
||||
std::snprintf(buf.data(), buf.size(), "%.3e", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
case 3:
|
||||
std::snprintf(buf.data(), buf.size(), "%.25g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
default:
|
||||
std::snprintf(buf.data(), buf.size(), "%.0f", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
}
|
||||
tokens.emplace_back(buf.data());
|
||||
}
|
||||
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
CAPTURE(token);
|
||||
const std::string text = "[" + token + "]";
|
||||
const double a = json_document::parse(text).root()[0].get<double>();
|
||||
const double b = json::parse(text)[0].get<double>();
|
||||
CHECK(std::memcmp(&a, &b, sizeof(double)) == 0);
|
||||
if (std::abs(b) < 1e38)
|
||||
{
|
||||
const float fa = nlohmann::basic_json_document<json_float>::parse(text).root()[0].get<float>();
|
||||
const float fb = json_float::parse(text)[0].get<float>();
|
||||
CHECK(std::memcmp(&fa, &fb, sizeof(float)) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("number tokens")
|
||||
{
|
||||
const json_document d = json_document::parse(R"([1.50, 1E2, -0, 123456789012345678901234567890, -12, 7, "x"])");
|
||||
const json_view v = d.root();
|
||||
CHECK(v[0].number_token() == "1.50");
|
||||
CHECK(v[1].number_token() == "1E2");
|
||||
CHECK(v[2].number_token() == "-0");
|
||||
CHECK(v[3].number_token() == "123456789012345678901234567890");
|
||||
CHECK(v[4].number_token() == "-12");
|
||||
CHECK(v[5].number_token() == "7");
|
||||
CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("conversions")
|
||||
{
|
||||
const std::string text = R"({"name": "widget", "count": 3, "tags": ["a", "b\n"], "sizes": {"s": 1, "m": 2}, "pair": [1, "x"]})";
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
const json j = json::parse(text);
|
||||
|
||||
// user types with from_json, and other types, through basic_json
|
||||
const record r = v.get<record>();
|
||||
CHECK(r.name == "widget");
|
||||
CHECK(r.count == 3);
|
||||
CHECK((v["pair"].get<std::pair<int, std::string>>() == j["pair"].get<std::pair<int, std::string>>()));
|
||||
CHECK(v["tags"].get<std::list<std::string>>() == j["tags"].get<std::list<std::string>>());
|
||||
CHECK((v["sizes"].get<std::unordered_map<std::string, int>>() == j["sizes"].get<std::unordered_map<std::string, int>>()));
|
||||
CHECK(v["tags"].get<std::vector<std::string>>() == std::vector<std::string> {"a", "b\n"});
|
||||
|
||||
// views of the elements
|
||||
const auto views = v["tags"].get<std::vector<json_view>>();
|
||||
CHECK(views.size() == 2);
|
||||
CHECK(views[1].get_string() == "b\n");
|
||||
const auto members = v.get<std::map<std::string, json_view>>();
|
||||
CHECK(members.at("count").get<int>() == 3);
|
||||
CHECK(v.get<json_view>()["name"].get_string() == "widget");
|
||||
|
||||
// strings without a copy point into the source text
|
||||
CHECK(v["name"].get_string().data() == text.data() + text.find("widget"));
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
CHECK(v["name"].get<std::string_view>() == "widget");
|
||||
#endif
|
||||
|
||||
std::string name;
|
||||
int count = 0;
|
||||
CHECK(&v["name"].get_to(name) == &name);
|
||||
v["count"].get_to(count);
|
||||
CHECK(name == "widget");
|
||||
CHECK(count == 3);
|
||||
|
||||
// a duplicate key: the last value, as parse()
|
||||
CHECK((json_document::parse(R"({"a":1,"a":2})").root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));
|
||||
|
||||
const json_view invalid;
|
||||
CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&);
|
||||
CHECK(invalid.get<json>().is_discarded());
|
||||
}
|
||||
|
||||
SECTION("value")
|
||||
{
|
||||
const json_document d = json_document::parse(R"({"n": 1, "s": "text", "o": {"x": [10, 20]}})");
|
||||
const json_view v = d.root();
|
||||
const json j = v.materialize();
|
||||
CHECK(v.value("n", 0) == j.value("n", 0));
|
||||
CHECK(v.value("missing", 42) == j.value("missing", 42));
|
||||
CHECK(v.value("s", "default") == j.value("s", "default"));
|
||||
CHECK(v.value("missing", "default") == j.value("missing", "default"));
|
||||
CHECK(v.value(std::string("n"), 2.5) == j.value(std::string("n"), 2.5));
|
||||
CHECK(v.value(json::json_pointer("/o/x/1"), 0) == j.value(json::json_pointer("/o/x/1"), 0));
|
||||
CHECK(v.value(json::json_pointer("/o/x/5"), 0) == j.value(json::json_pointer("/o/x/5"), 0));
|
||||
CHECK(v.value(json::json_pointer("/o/y"), "none") == j.value(json::json_pointer("/o/y"), "none"));
|
||||
// with a JSON pointer, arrays can be asked as well
|
||||
CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0));
|
||||
CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view JSON pointers")
|
||||
{
|
||||
SECTION("every value of generated documents")
|
||||
{
|
||||
generator g;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
std::string text;
|
||||
g.value(text, 0);
|
||||
const ordered_json_document d = ordered_json_document::parse(text);
|
||||
if (has_duplicate_keys(d.root()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CAPTURE(text);
|
||||
const ordered_json j = ordered_json::parse(text);
|
||||
const ordered_json flat = j.flatten();
|
||||
for (const auto& leaf : flat.items())
|
||||
{
|
||||
// the leaf and each of its parents
|
||||
for (ordered_json::json_pointer p(leaf.key());; p = p.parent_pointer())
|
||||
{
|
||||
CAPTURE(p.to_string());
|
||||
CHECK(d.root()[p].materialize() == j[p]);
|
||||
CHECK(d.root().at(p).materialize() == j.at(p));
|
||||
CHECK(d.root().contains(p));
|
||||
if (p.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("errors are those of basic_json")
|
||||
{
|
||||
const std::string text = R"({"a": [1, {"b": null}], "c": "s", "": {"": 0}, "a~b": 1, "c/d": 2})";
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
const json j = v.materialize();
|
||||
for (const char* pointer :
|
||||
{"", "/", "//", "/a", "/a/0", "/a/1/b", "/a/-", "/a/01", "/a/00", "/a/1a", "/a/a", "/a/", "/a/2", "/a/99", "/a/99999999999999999999",
|
||||
"/a/18446744073709551615", "/a/-1", "/a/+1", "/a/ 1", "/x", "/c/x", "/a/0/x", "/a/1/b/c", "/a~0b", "/c~1d", "/c~1d/x", "/a/1/-"
|
||||
})
|
||||
{
|
||||
CAPTURE(pointer);
|
||||
const json::json_pointer p(pointer);
|
||||
const std::string at_error = without_path(exception_of([&] { static_cast<void>(j.at(p)); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
|
||||
if (at_error.empty())
|
||||
{
|
||||
CHECK(v.at(p).materialize() == j.at(p));
|
||||
CHECK(v[p].materialize() == j[p]);
|
||||
}
|
||||
else if (at_error.find("out_of_range.401") != std::string::npos || at_error.find("out_of_range.403") != std::string::npos)
|
||||
{
|
||||
// undefined behavior for const basic_json::operator[]
|
||||
CHECK(!v[p]);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == without_path(exception_of([&] { static_cast<void>(j[p]); })));
|
||||
}
|
||||
// (basic_json::contains() throws out_of_range.404 for an empty
|
||||
// array index token, although it is not meant to throw; the view
|
||||
// answers false)
|
||||
const std::string contains_error = exception_of([&] { static_cast<void>(j.contains(p)); });
|
||||
CHECK(v.contains(p) == (contains_error.empty() && j.contains(p)));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast<void>(j.value(p, 5)); })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user