mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 19:50:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc9370c291 | ||
|
|
666801a2c6 |
@@ -224,5 +224,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
|
||||
1. Added in version 1.0.0.
|
||||
2. Added in version 1.0.0.
|
||||
3. Added in version 3.11.0.
|
||||
3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as
|
||||
already supported by [`operator[]`](operator[].md), [`value`](value.md), [`find`](find.md), and other lookup
|
||||
functions.
|
||||
4. Added in version 2.0.0.
|
||||
|
||||
@@ -119,7 +119,9 @@ Logarithmic in the size of the JSON object.
|
||||
## Version history
|
||||
|
||||
1. Added in version 3.11.0.
|
||||
2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0.
|
||||
2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0. Fixed in
|
||||
version 3.13.0 to consistently accept `std::string_view`-convertible keys, as already supported by
|
||||
[`operator[]`](operator[].md), [`at`](at.md), [`value`](value.md), and other lookup functions.
|
||||
3. Added in version 3.7.0.
|
||||
4. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of
|
||||
causing undefined behavior at runtime.
|
||||
|
||||
@@ -84,6 +84,8 @@ Logarithmic in the size of the JSON object.
|
||||
## Version history
|
||||
|
||||
1. Added in version 3.11.0.
|
||||
2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0.
|
||||
2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0. Fixed in version 3.13.0 to
|
||||
consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md),
|
||||
[`at`](at.md), [`value`](value.md), and other lookup functions.
|
||||
3. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time instead of
|
||||
causing undefined behavior at runtime.
|
||||
|
||||
@@ -213,5 +213,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
1. Added in version 1.0.0. Added support for binary types in version 3.8.0.
|
||||
2. Added in version 1.0.0. Added support for binary types in version 3.8.0.
|
||||
3. Added in version 1.0.0.
|
||||
4. Added in version 3.11.0.
|
||||
4. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as
|
||||
already supported by [`operator[]`](operator[].md), [`at`](at.md), [`value`](value.md), and other lookup
|
||||
functions.
|
||||
5. Added in version 1.0.0.
|
||||
|
||||
@@ -88,6 +88,8 @@ Logarithmic in the size of the JSON object.
|
||||
## Version history
|
||||
|
||||
1. Added in version 3.11.0.
|
||||
2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0.
|
||||
2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0. Fixed in version 3.13.0 to
|
||||
consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md),
|
||||
[`at`](at.md), [`value`](value.md), and other lookup functions.
|
||||
3. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of
|
||||
causing undefined behavior at runtime.
|
||||
|
||||
@@ -195,7 +195,9 @@ changes to any JSON value.
|
||||
1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version
|
||||
3.11.0. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time
|
||||
instead of causing undefined behavior at runtime.
|
||||
2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2.
|
||||
2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2. Fixed in version 3.13.0
|
||||
to consistently accept `std::string_view`-convertible keys, as already supported by
|
||||
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and other lookup functions.
|
||||
3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving
|
||||
`ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or
|
||||
`default_value`, as documented).
|
||||
|
||||
@@ -189,37 +189,6 @@ struct actual_object_comparator
|
||||
template<typename BasicJsonType>
|
||||
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
|
||||
|
||||
template<typename T>
|
||||
using detect_key_comp = decltype(std::declval<const T&>().key_comp());
|
||||
|
||||
// whether ObjectType can be constructed from a pair of Iterator together with
|
||||
// a copy of its own comparator, the way std::map can: it needs a nested
|
||||
// key_compare, a const key_comp() convertible to it, and a matching
|
||||
// (Iterator, Iterator, const key_compare&) constructor.
|
||||
//
|
||||
// used to preserve a stateful comparator when a copy is built from a range
|
||||
// past the iterative deep copy's nesting bound (see copy_object_level); an
|
||||
// object type that does not satisfy this, such as nlohmann::ordered_map
|
||||
// (which has key_compare for its std::map-like interface, but no key_comp()),
|
||||
// keeps default-constructing its comparator, just as it always has
|
||||
template<typename ObjectType, typename Iterator, typename = void>
|
||||
struct is_comparator_constructible_object_type_impl : std::false_type {};
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
struct is_comparator_constructible_object_type_impl <
|
||||
ObjectType, Iterator, enable_if_t<is_detected<detect_key_compare, ObjectType>::value >>
|
||||
{
|
||||
using key_compare = typename ObjectType::key_compare;
|
||||
|
||||
static constexpr bool value =
|
||||
is_detected_convertible<key_compare, detect_key_comp, ObjectType>::value &&
|
||||
std::is_constructible<ObjectType, Iterator, Iterator, const key_compare&>::value;
|
||||
};
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
struct is_comparator_constructible_object_type
|
||||
: is_comparator_constructible_object_type_impl<ObjectType, Iterator> {};
|
||||
|
||||
/////////////////
|
||||
// char_traits //
|
||||
/////////////////
|
||||
@@ -779,6 +748,30 @@ using is_usable_as_key_type = typename std::conditional <
|
||||
std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// type trait to check if KeyType can only be used as an object key after
|
||||
// converting it to std::string_view: it is convertible to std::string_view, the
|
||||
// object's comparator cannot compare it with object_t::key_type directly, but
|
||||
// can compare a std::string_view. JSON pointers and JSON iterators are ruled out
|
||||
// first, so that the conversion checks are never instantiated for them (a JSON
|
||||
// pointer's deprecated conversion to string_t would be named otherwise).
|
||||
template < typename BasicJsonType, typename KeyTypeCVRef, typename KeyType = uncvref_t<KeyTypeCVRef>,
|
||||
bool = is_json_pointer<KeyType>::value || is_json_iterator_of<BasicJsonType, KeyType>::value >
|
||||
struct is_string_view_convertible_key_type : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename KeyTypeCVRef, typename KeyType>
|
||||
struct is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef, KeyType, false>
|
||||
: std::integral_constant < bool,
|
||||
std::is_convertible<KeyTypeCVRef, std::string_view>::value
|
||||
&& !is_usable_as_key_type<typename BasicJsonType::object_comparator_t,
|
||||
typename BasicJsonType::object_t::key_type, KeyTypeCVRef, true, false>::value
|
||||
&& is_usable_as_key_type<typename BasicJsonType::object_comparator_t,
|
||||
typename BasicJsonType::object_t::key_type, std::string_view, true, false>::value > {};
|
||||
#else
|
||||
template<typename BasicJsonType, typename KeyTypeCVRef>
|
||||
struct is_string_view_convertible_key_type : std::false_type {};
|
||||
#endif
|
||||
|
||||
// type trait to check if KeyType can be used as an object key
|
||||
// true if:
|
||||
// - KeyType is comparable with BasicJsonType::object_t::key_type
|
||||
@@ -792,9 +785,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional <
|
||||
typename BasicJsonType::object_t::key_type, KeyTypeCVRef,
|
||||
RequireTransparentComparator, ExcludeObjectKeyType>::value
|
||||
&& !is_json_iterator_of<BasicJsonType, KeyType>::value)
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
|| std::is_convertible<KeyType, std::string_view>::value
|
||||
#endif
|
||||
|| is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef>::value
|
||||
, std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
|
||||
+33
-35
@@ -807,6 +807,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return it;
|
||||
}
|
||||
|
||||
/// @brief the key to look up an object member with: the key itself, or its
|
||||
/// std::string_view if the object can only be searched with that
|
||||
template < typename KeyType, detail::enable_if_t <
|
||||
!detail::is_string_view_convertible_key_type<basic_json_t, KeyType>::value, int > = 0 >
|
||||
static KeyType && lookup_key(KeyType && key) noexcept
|
||||
{
|
||||
return std::forward<KeyType>(key);
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
template < typename KeyType, detail::enable_if_t <
|
||||
detail::is_string_view_convertible_key_type<basic_json_t, KeyType>::value, int > = 0 >
|
||||
static std::string_view lookup_key(KeyType && key)
|
||||
{
|
||||
return std::forward<KeyType>(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief erase an element from the object and return the following one
|
||||
/// Not every map returns an iterator from erase(iterator): some containers
|
||||
/// (e.g., Abseil's hash maps) return void to avoid computing a successor
|
||||
@@ -1127,27 +1145,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief create the object type from a range, preserving @a src_object's
|
||||
/// comparator when the object type supports it
|
||||
/// Enabled for object types that provide a key_comp() and a matching
|
||||
/// range-plus-comparator constructor, such as std::map. Other object
|
||||
/// types, such as nlohmann::ordered_map, fall back to the plain range
|
||||
/// constructor and default-construct their comparator, just as they
|
||||
/// always have (@ref detail::is_comparator_constructible_object_type).
|
||||
template<typename Iterator, detail::enable_if_t<
|
||||
detail::is_comparator_constructible_object_type<object_t, Iterator>::value, int> = 0>
|
||||
static object_t* create_object_with_comparator(const object_t& src_object, Iterator first, Iterator last)
|
||||
{
|
||||
return create<object_t>(first, last, src_object.key_comp());
|
||||
}
|
||||
|
||||
template<typename Iterator, detail::enable_if_t<
|
||||
detail::negation<detail::is_comparator_constructible_object_type<object_t, Iterator>>::value, int> = 0>
|
||||
static object_t* create_object_with_comparator(const object_t& /*src_object*/, Iterator first, Iterator last)
|
||||
{
|
||||
return create<object_t>(first, last);
|
||||
}
|
||||
|
||||
/// @brief create the copy of the object @a src in @a dst
|
||||
/// @note structured values are appended to @a worklist instead
|
||||
static void copy_object_level(const basic_json& src, basic_json& dst,
|
||||
@@ -1165,8 +1162,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
scratch.emplace_back(element.first, basic_json());
|
||||
}
|
||||
|
||||
dst.m_data.m_value.object = create_object_with_comparator(src_object,
|
||||
std::make_move_iterator(scratch.begin()),
|
||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
||||
std::make_move_iterator(scratch.end()));
|
||||
// only now that the object exists may dst stop being a null value
|
||||
dst.m_data.m_type = value_t::object;
|
||||
@@ -2789,7 +2785,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
|
||||
}
|
||||
|
||||
auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
if (it == m_data.m_value.object->end())
|
||||
{
|
||||
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
|
||||
@@ -2827,7 +2823,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
|
||||
}
|
||||
|
||||
auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
if (it == m_data.m_value.object->end())
|
||||
{
|
||||
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
|
||||
@@ -2970,7 +2966,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// operator[] only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
auto result = m_data.m_value.object->emplace(std::forward<KeyType>(key), nullptr);
|
||||
auto result = m_data.m_value.object->emplace(lookup_key(std::forward<KeyType>(key)), nullptr);
|
||||
return set_parent(result.first->second);
|
||||
}
|
||||
|
||||
@@ -2986,7 +2982,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// const operator[] only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
JSON_ASSERT(it != m_data.m_value.object->end());
|
||||
return it->second;
|
||||
}
|
||||
@@ -2996,8 +2992,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
private:
|
||||
template<typename KeyType>
|
||||
using is_comparable_with_object_key = detail::is_comparable <
|
||||
object_comparator_t, const typename object_t::key_type&, KeyType >;
|
||||
using is_comparable_with_object_key = std::integral_constant < bool,
|
||||
detail::is_comparable <
|
||||
object_comparator_t, const typename object_t::key_type&, KeyType >::value
|
||||
|| detail::is_string_view_convertible_key_type<basic_json_t, KeyType>::value >;
|
||||
|
||||
template<typename ValueType>
|
||||
using value_return_type = std::conditional <
|
||||
@@ -3384,7 +3382,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
|
||||
}
|
||||
|
||||
const auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
const auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
if (it != m_data.m_value.object->end())
|
||||
{
|
||||
m_data.m_value.object->erase(it);
|
||||
@@ -3411,7 +3409,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||
size_type erase(KeyType && key)
|
||||
{
|
||||
return erase_internal(std::forward<KeyType>(key));
|
||||
return erase_internal(lookup_key(std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
/// @brief remove element from a JSON array given an index
|
||||
@@ -3491,7 +3489,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
if (is_object())
|
||||
{
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -3507,7 +3505,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
if (is_object())
|
||||
{
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -3530,7 +3528,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
size_type count(KeyType && key) const
|
||||
{
|
||||
// return 0 for all nonobject types
|
||||
return is_object() ? m_data.m_value.object->count(std::forward<KeyType>(key)) : 0;
|
||||
return is_object() ? m_data.m_value.object->count(lookup_key(std::forward<KeyType>(key))) : 0;
|
||||
}
|
||||
|
||||
/// @brief check the existence of an element in a JSON object
|
||||
@@ -3548,7 +3546,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
bool contains(KeyType && key) const
|
||||
{
|
||||
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
|
||||
return is_object() && m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key))) != m_data.m_value.object->end();
|
||||
}
|
||||
|
||||
/// @brief check the existence of an element in a JSON object given a JSON pointer
|
||||
|
||||
@@ -4197,37 +4197,6 @@ struct actual_object_comparator
|
||||
template<typename BasicJsonType>
|
||||
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
|
||||
|
||||
template<typename T>
|
||||
using detect_key_comp = decltype(std::declval<const T&>().key_comp());
|
||||
|
||||
// whether ObjectType can be constructed from a pair of Iterator together with
|
||||
// a copy of its own comparator, the way std::map can: it needs a nested
|
||||
// key_compare, a const key_comp() convertible to it, and a matching
|
||||
// (Iterator, Iterator, const key_compare&) constructor.
|
||||
//
|
||||
// used to preserve a stateful comparator when a copy is built from a range
|
||||
// past the iterative deep copy's nesting bound (see copy_object_level); an
|
||||
// object type that does not satisfy this, such as nlohmann::ordered_map
|
||||
// (which has key_compare for its std::map-like interface, but no key_comp()),
|
||||
// keeps default-constructing its comparator, just as it always has
|
||||
template<typename ObjectType, typename Iterator, typename = void>
|
||||
struct is_comparator_constructible_object_type_impl : std::false_type {};
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
struct is_comparator_constructible_object_type_impl <
|
||||
ObjectType, Iterator, enable_if_t<is_detected<detect_key_compare, ObjectType>::value >>
|
||||
{
|
||||
using key_compare = typename ObjectType::key_compare;
|
||||
|
||||
static constexpr bool value =
|
||||
is_detected_convertible<key_compare, detect_key_comp, ObjectType>::value &&
|
||||
std::is_constructible<ObjectType, Iterator, Iterator, const key_compare&>::value;
|
||||
};
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
struct is_comparator_constructible_object_type
|
||||
: is_comparator_constructible_object_type_impl<ObjectType, Iterator> {};
|
||||
|
||||
/////////////////
|
||||
// char_traits //
|
||||
/////////////////
|
||||
@@ -4787,6 +4756,30 @@ using is_usable_as_key_type = typename std::conditional <
|
||||
std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// type trait to check if KeyType can only be used as an object key after
|
||||
// converting it to std::string_view: it is convertible to std::string_view, the
|
||||
// object's comparator cannot compare it with object_t::key_type directly, but
|
||||
// can compare a std::string_view. JSON pointers and JSON iterators are ruled out
|
||||
// first, so that the conversion checks are never instantiated for them (a JSON
|
||||
// pointer's deprecated conversion to string_t would be named otherwise).
|
||||
template < typename BasicJsonType, typename KeyTypeCVRef, typename KeyType = uncvref_t<KeyTypeCVRef>,
|
||||
bool = is_json_pointer<KeyType>::value || is_json_iterator_of<BasicJsonType, KeyType>::value >
|
||||
struct is_string_view_convertible_key_type : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename KeyTypeCVRef, typename KeyType>
|
||||
struct is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef, KeyType, false>
|
||||
: std::integral_constant < bool,
|
||||
std::is_convertible<KeyTypeCVRef, std::string_view>::value
|
||||
&& !is_usable_as_key_type<typename BasicJsonType::object_comparator_t,
|
||||
typename BasicJsonType::object_t::key_type, KeyTypeCVRef, true, false>::value
|
||||
&& is_usable_as_key_type<typename BasicJsonType::object_comparator_t,
|
||||
typename BasicJsonType::object_t::key_type, std::string_view, true, false>::value > {};
|
||||
#else
|
||||
template<typename BasicJsonType, typename KeyTypeCVRef>
|
||||
struct is_string_view_convertible_key_type : std::false_type {};
|
||||
#endif
|
||||
|
||||
// type trait to check if KeyType can be used as an object key
|
||||
// true if:
|
||||
// - KeyType is comparable with BasicJsonType::object_t::key_type
|
||||
@@ -4800,9 +4793,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional <
|
||||
typename BasicJsonType::object_t::key_type, KeyTypeCVRef,
|
||||
RequireTransparentComparator, ExcludeObjectKeyType>::value
|
||||
&& !is_json_iterator_of<BasicJsonType, KeyType>::value)
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
|| std::is_convertible<KeyType, std::string_view>::value
|
||||
#endif
|
||||
|| is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef>::value
|
||||
, std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
@@ -27765,6 +27756,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return it;
|
||||
}
|
||||
|
||||
/// @brief the key to look up an object member with: the key itself, or its
|
||||
/// std::string_view if the object can only be searched with that
|
||||
template < typename KeyType, detail::enable_if_t <
|
||||
!detail::is_string_view_convertible_key_type<basic_json_t, KeyType>::value, int > = 0 >
|
||||
static KeyType && lookup_key(KeyType && key) noexcept
|
||||
{
|
||||
return std::forward<KeyType>(key);
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
template < typename KeyType, detail::enable_if_t <
|
||||
detail::is_string_view_convertible_key_type<basic_json_t, KeyType>::value, int > = 0 >
|
||||
static std::string_view lookup_key(KeyType && key)
|
||||
{
|
||||
return std::forward<KeyType>(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief erase an element from the object and return the following one
|
||||
/// Not every map returns an iterator from erase(iterator): some containers
|
||||
/// (e.g., Abseil's hash maps) return void to avoid computing a successor
|
||||
@@ -28085,27 +28094,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief create the object type from a range, preserving @a src_object's
|
||||
/// comparator when the object type supports it
|
||||
/// Enabled for object types that provide a key_comp() and a matching
|
||||
/// range-plus-comparator constructor, such as std::map. Other object
|
||||
/// types, such as nlohmann::ordered_map, fall back to the plain range
|
||||
/// constructor and default-construct their comparator, just as they
|
||||
/// always have (@ref detail::is_comparator_constructible_object_type).
|
||||
template<typename Iterator, detail::enable_if_t<
|
||||
detail::is_comparator_constructible_object_type<object_t, Iterator>::value, int> = 0>
|
||||
static object_t* create_object_with_comparator(const object_t& src_object, Iterator first, Iterator last)
|
||||
{
|
||||
return create<object_t>(first, last, src_object.key_comp());
|
||||
}
|
||||
|
||||
template<typename Iterator, detail::enable_if_t<
|
||||
detail::negation<detail::is_comparator_constructible_object_type<object_t, Iterator>>::value, int> = 0>
|
||||
static object_t* create_object_with_comparator(const object_t& /*src_object*/, Iterator first, Iterator last)
|
||||
{
|
||||
return create<object_t>(first, last);
|
||||
}
|
||||
|
||||
/// @brief create the copy of the object @a src in @a dst
|
||||
/// @note structured values are appended to @a worklist instead
|
||||
static void copy_object_level(const basic_json& src, basic_json& dst,
|
||||
@@ -28123,8 +28111,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
scratch.emplace_back(element.first, basic_json());
|
||||
}
|
||||
|
||||
dst.m_data.m_value.object = create_object_with_comparator(src_object,
|
||||
std::make_move_iterator(scratch.begin()),
|
||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
||||
std::make_move_iterator(scratch.end()));
|
||||
// only now that the object exists may dst stop being a null value
|
||||
dst.m_data.m_type = value_t::object;
|
||||
@@ -29747,7 +29734,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
|
||||
}
|
||||
|
||||
auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
if (it == m_data.m_value.object->end())
|
||||
{
|
||||
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
|
||||
@@ -29785,7 +29772,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this));
|
||||
}
|
||||
|
||||
auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
if (it == m_data.m_value.object->end())
|
||||
{
|
||||
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
|
||||
@@ -29928,7 +29915,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// operator[] only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
auto result = m_data.m_value.object->emplace(std::forward<KeyType>(key), nullptr);
|
||||
auto result = m_data.m_value.object->emplace(lookup_key(std::forward<KeyType>(key)), nullptr);
|
||||
return set_parent(result.first->second);
|
||||
}
|
||||
|
||||
@@ -29944,7 +29931,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// const operator[] only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
JSON_ASSERT(it != m_data.m_value.object->end());
|
||||
return it->second;
|
||||
}
|
||||
@@ -29954,8 +29941,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
private:
|
||||
template<typename KeyType>
|
||||
using is_comparable_with_object_key = detail::is_comparable <
|
||||
object_comparator_t, const typename object_t::key_type&, KeyType >;
|
||||
using is_comparable_with_object_key = std::integral_constant < bool,
|
||||
detail::is_comparable <
|
||||
object_comparator_t, const typename object_t::key_type&, KeyType >::value
|
||||
|| detail::is_string_view_convertible_key_type<basic_json_t, KeyType>::value >;
|
||||
|
||||
template<typename ValueType>
|
||||
using value_return_type = std::conditional <
|
||||
@@ -30342,7 +30331,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
|
||||
}
|
||||
|
||||
const auto it = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
const auto it = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
if (it != m_data.m_value.object->end())
|
||||
{
|
||||
m_data.m_value.object->erase(it);
|
||||
@@ -30369,7 +30358,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||
size_type erase(KeyType && key)
|
||||
{
|
||||
return erase_internal(std::forward<KeyType>(key));
|
||||
return erase_internal(lookup_key(std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
/// @brief remove element from a JSON array given an index
|
||||
@@ -30449,7 +30438,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
if (is_object())
|
||||
{
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -30465,7 +30454,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
if (is_object())
|
||||
{
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));
|
||||
result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -30488,7 +30477,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
size_type count(KeyType && key) const
|
||||
{
|
||||
// return 0 for all nonobject types
|
||||
return is_object() ? m_data.m_value.object->count(std::forward<KeyType>(key)) : 0;
|
||||
return is_object() ? m_data.m_value.object->count(lookup_key(std::forward<KeyType>(key))) : 0;
|
||||
}
|
||||
|
||||
/// @brief check the existence of an element in a JSON object
|
||||
@@ -30506,7 +30495,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
bool contains(KeyType && key) const
|
||||
{
|
||||
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
|
||||
return is_object() && m_data.m_value.object->find(lookup_key(std::forward<KeyType>(key))) != m_data.m_value.object->end();
|
||||
}
|
||||
|
||||
/// @brief check the existence of an element in a JSON object given a JSON pointer
|
||||
|
||||
@@ -827,46 +827,6 @@ Json nest(Json j, const std::size_t depth)
|
||||
return j;
|
||||
}
|
||||
|
||||
// a std::map comparator with state: case-insensitive, unless constructed
|
||||
// case-sensitive. Used to check that copying an object copies the original's
|
||||
// comparator rather than default-constructing a new one (see #5649).
|
||||
struct key_case_less
|
||||
{
|
||||
key_case_less() = default;
|
||||
explicit key_case_less(const bool cs) noexcept : case_sensitive(cs) {}
|
||||
|
||||
bool operator()(const std::string& a, const std::string& b) const
|
||||
{
|
||||
if (case_sensitive)
|
||||
{
|
||||
return a < b;
|
||||
}
|
||||
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
||||
[](unsigned char x, unsigned char y)
|
||||
{
|
||||
return std::tolower(x) < std::tolower(y);
|
||||
});
|
||||
}
|
||||
|
||||
bool case_sensitive = false;
|
||||
};
|
||||
|
||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
||||
using key_case_json = nlohmann::basic_json<key_case_map>;
|
||||
|
||||
// the innermost value of a chain of single-element arrays
|
||||
template<typename Json>
|
||||
const Json& innermost(const Json& j)
|
||||
{
|
||||
const Json* p = &j;
|
||||
while (p->is_array())
|
||||
{
|
||||
p = &(*p)[0];
|
||||
}
|
||||
return *p;
|
||||
}
|
||||
|
||||
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
||||
// (neither less than the other) although they are not equal
|
||||
struct case_insensitive_less
|
||||
@@ -931,47 +891,6 @@ TEST_CASE("equality of objects whose entries have no fixed order")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("copying an object preserves its comparator's state")
|
||||
{
|
||||
// Past the iterative deep copy's nesting bound, an object copy used to be
|
||||
// built with a default-constructed comparator instead of a copy of the
|
||||
// original's. For an object type whose comparator carries state - here, a
|
||||
// std::map that compares keys case-sensitively only when created that way
|
||||
// - this reordered the copy's keys and could even drop entries that the
|
||||
// original's comparator kept distinct (see #5649).
|
||||
key_case_json object = key_case_json::object_t(key_case_less(true)); // case-sensitive
|
||||
object["b"] = 1;
|
||||
object["B"] = 2;
|
||||
object["a"] = 3;
|
||||
REQUIRE(object.dump() == R"({"B":2,"a":3,"b":1})");
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
|
||||
key_case_json original = object;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
original = key_case_json::array({std::move(original)});
|
||||
}
|
||||
|
||||
{
|
||||
const key_case_json copy = original; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(innermost(copy).size() == 3);
|
||||
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
||||
CHECK(copy == original);
|
||||
}
|
||||
|
||||
{
|
||||
key_case_json copy = key_case_json::array();
|
||||
copy = original;
|
||||
CHECK(innermost(copy).size() == 3);
|
||||
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
||||
CHECK(copy == original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("equality of an object whose comparator treats different keys as equivalent")
|
||||
{
|
||||
// https://github.com/nlohmann/json/issues/5655: past the nesting bound,
|
||||
|
||||
@@ -1973,4 +1973,115 @@ TEST_CASE("operator[] with user-defined std::string_view-convertible types")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("keys convertible to std::string_view work with all lookup functions (regression test for #5663)")
|
||||
{
|
||||
// a key type convertible only to std::string_view: the case #4958 added
|
||||
// support for, but only the non-const operator[] compiled with it
|
||||
struct ViewKey
|
||||
{
|
||||
operator std::string_view() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
};
|
||||
|
||||
// a key type convertible to both std::string and std::string_view: with
|
||||
// 3.12.0, such a key worked with at, the const operator[], find, count and
|
||||
// contains via the conversion to std::string; #4958 made the KeyType&&
|
||||
// templates win overload resolution for it instead, and those then failed
|
||||
struct DualKey
|
||||
{
|
||||
operator std::string() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
operator std::string_view() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
};
|
||||
|
||||
SECTION("nlohmann::json")
|
||||
{
|
||||
using json = nlohmann::json;
|
||||
|
||||
SECTION("ViewKey")
|
||||
{
|
||||
json j = {{"a", 1}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(j[ViewKey{}] == 1);
|
||||
CHECK(cj[ViewKey{}] == 1);
|
||||
CHECK(j.at(ViewKey{}) == 1);
|
||||
CHECK(cj.at(ViewKey{}) == 1);
|
||||
CHECK(j.find(ViewKey{}) != j.end());
|
||||
CHECK(cj.find(ViewKey{}) != cj.end());
|
||||
CHECK(j.count(ViewKey{}) == 1);
|
||||
CHECK(j.contains(ViewKey{}));
|
||||
CHECK(j.value(ViewKey{}, 0) == 1);
|
||||
CHECK(j.erase(ViewKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
|
||||
SECTION("DualKey")
|
||||
{
|
||||
json j = {{"a", 1}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(j[DualKey{}] == 1);
|
||||
CHECK(cj[DualKey{}] == 1);
|
||||
CHECK(j.at(DualKey{}) == 1);
|
||||
CHECK(cj.at(DualKey{}) == 1);
|
||||
CHECK(j.find(DualKey{}) != j.end());
|
||||
CHECK(cj.find(DualKey{}) != cj.end());
|
||||
CHECK(j.count(DualKey{}) == 1);
|
||||
CHECK(j.contains(DualKey{}));
|
||||
CHECK(j.value(DualKey{}, 0) == 1);
|
||||
CHECK(j.erase(DualKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("nlohmann::ordered_json")
|
||||
{
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
SECTION("ViewKey")
|
||||
{
|
||||
ordered_json j = {{"a", 1}};
|
||||
const ordered_json& cj = j;
|
||||
|
||||
CHECK(j[ViewKey{}] == 1);
|
||||
CHECK(cj[ViewKey{}] == 1);
|
||||
CHECK(j.at(ViewKey{}) == 1);
|
||||
CHECK(cj.at(ViewKey{}) == 1);
|
||||
CHECK(j.find(ViewKey{}) != j.end());
|
||||
CHECK(cj.find(ViewKey{}) != cj.end());
|
||||
CHECK(j.count(ViewKey{}) == 1);
|
||||
CHECK(j.contains(ViewKey{}));
|
||||
CHECK(j.value(ViewKey{}, 0) == 1);
|
||||
CHECK(j.erase(ViewKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
|
||||
SECTION("DualKey")
|
||||
{
|
||||
ordered_json j = {{"a", 1}};
|
||||
const ordered_json& cj = j;
|
||||
|
||||
CHECK(j[DualKey{}] == 1);
|
||||
CHECK(cj[DualKey{}] == 1);
|
||||
CHECK(j.at(DualKey{}) == 1);
|
||||
CHECK(cj.at(DualKey{}) == 1);
|
||||
CHECK(j.find(DualKey{}) != j.end());
|
||||
CHECK(cj.find(DualKey{}) != cj.end());
|
||||
CHECK(j.count(DualKey{}) == 1);
|
||||
CHECK(j.contains(DualKey{}));
|
||||
CHECK(j.value(DualKey{}, 0) == 1);
|
||||
CHECK(j.erase(DualKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user