1
0
mirror of https://github.com/nlohmann/json.git synced 2025-07-28 12:02:00 +03:00

Implement support for string_view (attempt no. 3) (#3423)

* Add key_compare member to ordered_map

* Replace == with key_compare in ordered_map

* Expose the actual comparison function used by object_t

nlohmann::ordered_map uses a different comparison function than the one
provided via template parameter.
* Introduce a type trait to detect if object_t has a key_compare member.
* Rename object_comparator_t to default_object_comparator_t.
* Add object_comparator_t to be conditionally defined as
  object_t::key_compare, if available, or default_object_comparator_t
  otherwise.
* Update the documentation accordingly.

Co-authored-by: Niels Lohmann <niels.lohmann@gmail.com>

* Add type traits to check if a type is usable as object key

Add type trait to check:
* if a type is a specialization of a template.
* if a type is a json_pointer.
* if a type is a basic_json::{const_,}iterator.
* if two types are comparable using a given comparison functor.
* if a type is comparable to basic_json::object_t::key_type.
* if a type has a member type is_transparent.
* if a type is usable as object key.
* if a type has an erase() function accepting a given KeyType.

Co-authored-by: Niels Lohmann <niels.lohmann@gmail.com>

* Rework basic_json element access to accept more key types

Rework basic_json element access member functions and operators to
accept any type that meets the requirements defined by type trait
detail::is_usable_as_key_type.

Member functions and operators:
* at()
* operator[]
* value()
* erase()
* find()
* count()
* contains()

Update documentation to reflect these changes.

Add unit tests to excercise the new functions using std::string_view.

Co-authored-by: Niels Lohmann <niels.lohmann@gmail.com>

Co-authored-by: Niels Lohmann <niels.lohmann@gmail.com>
This commit is contained in:
Florian Albrechtskirchinger
2022-04-29 21:40:02 +02:00
committed by GitHub
parent ee51661481
commit 5352856f04
26 changed files with 1517 additions and 305 deletions

View File

@ -350,21 +350,23 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// the template arguments passed to class @ref basic_json.
/// @{
/// @brief object key comparator type
/// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/
/// @brief default object key comparator type
/// The actual object key comparator type (@ref object_comparator_t) may be
/// different.
/// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/
#if defined(JSON_HAS_CPP_14)
// Use transparent comparator if possible, combined with perfect forwarding
// on find() and count() calls prevents unnecessary string construction.
using object_comparator_t = std::less<>;
// use of transparent comparator avoids unnecessary repeated construction of temporaries
// in functions involving lookup by key with types other than object_t::key_type (aka. StringType)
using default_object_comparator_t = std::less<>;
#else
using object_comparator_t = std::less<StringType>;
using default_object_comparator_t = std::less<StringType>;
#endif
/// @brief a type for an object
/// @sa https://json.nlohmann.me/api/basic_json/object_t/
using object_t = ObjectType<StringType,
basic_json,
object_comparator_t,
default_object_comparator_t,
AllocatorType<std::pair<const StringType,
basic_json>>>;
@ -396,6 +398,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/binary_t/
using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;
/// @brief object key comparator type
/// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/
using object_comparator_t = detail::actual_object_comparator_t<basic_json>;
/// @}
private:
@ -2000,22 +2006,37 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
reference at(const typename object_t::key_type& key)
{
// at only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
JSON_TRY
{
return set_parent(m_value.object->at(key));
}
JSON_CATCH (std::out_of_range&)
{
// create better exception explanation
JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this));
}
}
else
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
}
auto it = m_value.object->find(key);
if (it == m_value.object->end())
{
JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this));
}
return set_parent(it->second);
}
/// @brief access specified object element with bounds checking
/// @sa https://json.nlohmann.me/api/basic_json/at/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
reference at(KeyType && key)
{
// at only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
}
auto it = m_value.object->find(std::forward<KeyType>(key));
if (it == m_value.object->end())
{
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
}
return set_parent(it->second);
}
/// @brief access specified object element with bounds checking
@ -2023,22 +2044,37 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const_reference at(const typename object_t::key_type& key) const
{
// at only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
JSON_TRY
{
return m_value.object->at(key);
}
JSON_CATCH (std::out_of_range&)
{
// create better exception explanation
JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this));
}
}
else
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
}
auto it = m_value.object->find(key);
if (it == m_value.object->end())
{
JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this));
}
return it->second;
}
/// @brief access specified object element with bounds checking
/// @sa https://json.nlohmann.me/api/basic_json/at/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
const_reference at(KeyType && key) const
{
// at only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
}
auto it = m_value.object->find(std::forward<KeyType>(key));
if (it == m_value.object->end())
{
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
}
return it->second;
}
/// @brief access specified array element
@ -2102,7 +2138,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief access specified object element
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
reference operator[](const typename object_t::key_type& key)
reference operator[](typename object_t::key_type key)
{
// implicitly convert null value to an empty object
if (is_null())
@ -2115,7 +2151,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
return set_parent(m_value.object->operator[](key));
auto result = m_value.object->emplace(std::move(key), nullptr);
return set_parent(result.first->second);
}
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this));
@ -2128,31 +2165,47 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
JSON_ASSERT(m_value.object->find(key) != m_value.object->end());
return m_value.object->find(key)->second;
auto it = m_value.object->find(key);
JSON_ASSERT(it != m_value.object->end());
return it->second;
}
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this));
}
/// @brief access specified object element
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
// these two functions resolve a (const) char * ambiguity affecting Clang and MSVC
// (they seemingly cannot be constrained to resolve the ambiguity)
template<typename T>
JSON_HEDLEY_NON_NULL(2)
reference operator[](T* key)
{
// implicitly convert null to object
return operator[](typename object_t::key_type(key));
}
template<typename T>
const_reference operator[](T* key) const
{
return operator[](typename object_t::key_type(key));
}
/// @brief access specified object element
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int > = 0 >
reference operator[](KeyType && key)
{
// implicitly convert null value to an empty object
if (is_null())
{
m_type = value_t::object;
m_value = value_t::object;
m_value.object = create<object_t>();
assert_invariant();
}
// at only works for objects
// operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
return set_parent(m_value.object->operator[](key));
auto result = m_value.object->emplace(std::forward<KeyType>(key), nullptr);
return set_parent(result.first->second);
}
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this));
@ -2160,15 +2213,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief access specified object element
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
template<typename T>
JSON_HEDLEY_NON_NULL(2)
const_reference operator[](T* key) const
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int > = 0 >
const_reference operator[](KeyType && key) const
{
// at only works for objects
// const operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
JSON_ASSERT(m_value.object->find(key) != m_value.object->end());
return m_value.object->find(key)->second;
auto it = m_value.object->find(std::forward<KeyType>(key));
JSON_ASSERT(it != m_value.object->end());
return it->second;
}
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this));
@ -2176,23 +2230,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief access specified object element with default value
/// @sa https://json.nlohmann.me/api/basic_json/value/
/// using std::is_convertible in a std::enable_if will fail when using explicit conversions
template < class ValueType, typename std::enable_if <
detail::is_getable<basic_json_t, ValueType>::value
&& !std::is_same<value_t, ValueType>::value, int >::type = 0 >
ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const
// this is the value(const typename object_t::key_type&) overload
template < class KeyType, class ValueType, detail::enable_if_t <
std::is_same<KeyType, typename object_t::key_type>::value
&& detail::is_getable<basic_json_t, ValueType>::value
&& !std::is_same<value_t, ValueType>::value, int > = 0 >
typename std::decay<ValueType>::type value(const KeyType& key, ValueType && default_value) const
{
// at only works for objects
// value only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
// if key is found, return value and given default value otherwise
const auto it = find(key);
if (it != end())
{
return it->template get<ValueType>();
return it->template get<typename std::decay<ValueType>::type>();
}
return default_value;
return std::forward<ValueType>(default_value);
}
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
@ -2206,13 +2261,64 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return value(key, string_t(default_value));
}
// these two functions, in conjunction with value(const KeyType &, ValueType &&),
// resolve an ambiguity that would otherwise occur between the json_pointer and
// typename object_t::key_type & overloads
template < class ValueType, detail::enable_if_t <
detail::is_getable<basic_json_t, ValueType>::value
&& !std::is_same<value_t, ValueType>::value, int > = 0 >
typename std::decay<ValueType>::type value(const char* key, ValueType && default_value) const
{
return value(typename object_t::key_type(key), std::forward<ValueType>(default_value));
}
string_t value(const char* key, const char* default_value) const
{
return value(typename object_t::key_type(key), string_t(default_value));
}
/// @brief access specified object element with default value
/// @sa https://json.nlohmann.me/api/basic_json/value/
/// using std::is_convertible in a std::enable_if will fail when using explicit conversions
template < class KeyType, class ValueType, detail::enable_if_t <
detail::is_getable<basic_json_t, ValueType>::value
&& !std::is_same<value_t, ValueType>::value
&& detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int > = 0 >
typename std::decay<ValueType>::type value(KeyType && key, ValueType && default_value) const
{
// value only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
// if key is found, return value and given default value otherwise
const auto it = find(std::forward<KeyType>(key));
if (it != end())
{
return it->template get<typename std::decay<ValueType>::type>();
}
return std::forward<ValueType>(default_value);
}
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
}
/// @brief access specified object element with default value
/// @sa https://json.nlohmann.me/api/basic_json/value/
/// overload for a default value of type const char*
template < class KeyType, detail::enable_if_t <
!detail::is_json_pointer<KeyType>::value, int > = 0 >
string_t value(KeyType && key, const char* default_value) const
{
return value(std::forward<KeyType>(key), string_t(default_value));
}
/// @brief access specified object element via JSON Pointer with default value
/// @sa https://json.nlohmann.me/api/basic_json/value/
template<class ValueType, typename std::enable_if<
detail::is_getable<basic_json_t, ValueType>::value, int>::type = 0>
template < class ValueType, detail::enable_if_t <
detail::is_getable<basic_json_t, ValueType>::value, int> = 0 >
ValueType value(const json_pointer& ptr, const ValueType& default_value) const
{
// at only works for objects
// value only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
// if pointer resolves a value, return it or use default value
@ -2229,8 +2335,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
}
template<class ValueType, class BasicJsonType, typename std::enable_if<
detail::is_getable<basic_json_t, ValueType>::value, int>::type = 0>
template < class ValueType, class BasicJsonType, detail::enable_if_t <
detail::is_getable<basic_json_t, ValueType>::value, int> = 0 >
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const
{
@ -2288,10 +2394,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief remove element given an iterator
/// @sa https://json.nlohmann.me/api/basic_json/erase/
template < class IteratorType, typename std::enable_if <
template < class IteratorType, detail::enable_if_t <
std::is_same<IteratorType, typename basic_json_t::iterator>::value ||
std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int >::type
= 0 >
std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 >
IteratorType erase(IteratorType pos)
{
// make sure iterator fits the current value
@ -2359,10 +2464,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief remove elements given an iterator range
/// @sa https://json.nlohmann.me/api/basic_json/erase/
template < class IteratorType, typename std::enable_if <
template < class IteratorType, detail::enable_if_t <
std::is_same<IteratorType, typename basic_json_t::iterator>::value ||
std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int >::type
= 0 >
std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 >
IteratorType erase(IteratorType first, IteratorType last)
{
// make sure iterator fits the current value
@ -2431,17 +2535,57 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result;
}
private:
template < typename KeyType, detail::enable_if_t <
detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >
size_type erase_internal(KeyType && key)
{
// this erase only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
}
return m_value.object->erase(std::forward<KeyType>(key));
}
template < typename KeyType, detail::enable_if_t <
!detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >
size_type erase_internal(KeyType && key)
{
// this erase only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
}
const auto it = m_value.object->find(std::forward<KeyType>(key));
if (it != m_value.object->end())
{
m_value.object->erase(it);
return 1;
}
return 0;
}
public:
/// @brief remove element from a JSON object given a key
/// @sa https://json.nlohmann.me/api/basic_json/erase/
size_type erase(const typename object_t::key_type& key)
{
// this erase only works for objects
if (JSON_HEDLEY_LIKELY(is_object()))
{
return m_value.object->erase(key);
}
// the indirection via erase_internal() is added to avoid making this
// function a template and thus de-rank it during overload resolution
return erase_internal(key);
}
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
/// @brief remove element from a JSON object given a key
/// @sa https://json.nlohmann.me/api/basic_json/erase/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
size_type erase(KeyType && key)
{
return erase_internal(std::forward<KeyType>(key));
}
/// @brief remove element from a JSON array given an index
@ -2476,14 +2620,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief find an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/find/
template<typename KeyT>
iterator find(KeyT&& key)
iterator find(const typename object_t::key_type& key)
{
auto result = end();
if (is_object())
{
result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
result.m_it.object_iterator = m_value.object->find(key);
}
return result;
@ -2491,14 +2634,45 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief find an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/find/
template<typename KeyT>
const_iterator find(KeyT&& key) const
const_iterator find(const typename object_t::key_type& key) const
{
auto result = cend();
if (is_object())
{
result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
result.m_it.object_iterator = m_value.object->find(key);
}
return result;
}
/// @brief find an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/find/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
iterator find(KeyType && key)
{
auto result = end();
if (is_object())
{
result.m_it.object_iterator = m_value.object->find(std::forward<KeyType>(key));
}
return result;
}
/// @brief find an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/find/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
const_iterator find(KeyType && key) const
{
auto result = cend();
if (is_object())
{
result.m_it.object_iterator = m_value.object->find(std::forward<KeyType>(key));
}
return result;
@ -2506,20 +2680,36 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of occurrences of a key in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/count/
template<typename KeyT>
size_type count(KeyT&& key) const
size_type count(const typename object_t::key_type& key) const
{
// return 0 for all nonobject types
return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0;
return is_object() ? m_value.object->count(key) : 0;
}
/// @brief returns the number of occurrences of a key in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/count/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
size_type count(KeyType && key) const
{
// return 0 for all nonobject types
return is_object() ? m_value.object->count(std::forward<KeyType>(key)) : 0;
}
/// @brief check the existence of an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/contains/
template < typename KeyT, typename std::enable_if <
!detail::is_json_pointer<typename std::decay<KeyT>::type>::value, int >::type = 0 >
bool contains(KeyT && key) const
bool contains(const typename object_t::key_type& key) const
{
return is_object() && m_value.object->find(std::forward<KeyT>(key)) != m_value.object->end();
return is_object() && m_value.object->find(key) != m_value.object->end();
}
/// @brief check the existence of an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/contains/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<basic_json_t, KeyType>::value, int> = 0>
bool contains(KeyType && key) const
{
return is_object() && m_value.object->find(std::forward<KeyType>(key)) != m_value.object->end();
}
/// @brief check the existence of an element in a JSON object given a JSON pointer
@ -4305,7 +4495,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.get_unchecked(this);
}
template<typename BasicJsonType>
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)
{
@ -4319,7 +4509,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.get_unchecked(this);
}
template<typename BasicJsonType>
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
{