Compare commits

..
Author SHA1 Message Date
Niels Lohmann bc9370c291 Merge branch 'develop' into claude/string-view-convertible-keys-5663
Conflicts:
- docs/mkdocs/docs/api/basic_json/count.md: kept the PR's string_view note on overload 2 and develop's new item 3 (deleted integral-key overload)
- docs/mkdocs/docs/api/basic_json/find.md: same as count.md
- docs/mkdocs/docs/api/basic_json/value.md: kept develop's integral-key note on item 1 and the PR's string_view note on item 2

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:34:35 +02:00
Niels Lohmann 666801a2c6 Fix key types convertible to std::string_view breaking lookups
Since #4958, a key type implicitly convertible to std::string_view was
accepted by is_usable_as_basic_json_key_type without checking that the
object's comparator can actually compare object_t::key_type with that
key type. The key was then forwarded unchanged to the underlying map,
so const operator[], at, find, count, contains, erase and value failed
to compile (a hard error inside <map>) for a key convertible only to
std::string_view, and value() rejected such keys outright. For keys
convertible to both std::string and std::string_view, the KeyType&&
templates now won overload resolution over the object_t::key_type
overloads and then failed the same way, a regression from 3.12.0. Only
the non-const operator[] worked, because it uses emplace(), which
constructs a std::string from the key explicitly. ordered_json was not
affected, since ordered_map checks comparability itself.

Add a trait, is_string_view_convertible_key_type, that recognizes a key
type that is convertible to std::string_view but not directly
comparable with the object's key type, provided std::string_view itself
is comparable with it. at(), operator[], find(), count(), contains(),
erase() and value() now route such keys through a new lookup_key()
helper that converts them to std::string_view before they reach the
object, matching how the object's transparent comparator already
supports std::string_view lookups.

Fixes #5663.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:31:37 +02:00
13 changed files with 257 additions and 96 deletions
+3 -1
View File
@@ -224,5 +224,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
1. Added in version 1.0.0. 1. Added in version 1.0.0.
2. 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. 4. Added in version 2.0.0.
+3 -1
View File
@@ -119,7 +119,9 @@ Logarithmic in the size of the JSON object.
## Version history ## Version history
1. Added in version 3.11.0. 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. 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 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. causing undefined behavior at runtime.
+3 -1
View File
@@ -84,6 +84,8 @@ Logarithmic in the size of the JSON object.
## Version history ## Version history
1. Added in version 3.11.0. 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 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. causing undefined behavior at runtime.
+3 -1
View File
@@ -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. 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. 2. Added in version 1.0.0. Added support for binary types in version 3.8.0.
3. Added in version 1.0.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. 5. Added in version 1.0.0.
+3 -1
View File
@@ -88,6 +88,8 @@ Logarithmic in the size of the JSON object.
## Version history ## Version history
1. Added in version 3.11.0. 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 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. causing undefined behavior at runtime.
+3 -11
View File
@@ -89,9 +89,6 @@ Strong exception safety: if an exception occurs, the original value stays intact
- Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed - Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed
JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms). JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms).
For the **const** version, an object key or array index in `ptr` that does not exist is not reported by an
exception, but is undefined behavior (see the notes below). Use [`at`](at.md) for checked access.
## Complexity ## Complexity
1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`. 1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`.
@@ -106,12 +103,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element
(see the notes below). (see the notes below).
1. If the element at index `idx` does not exist, the behavior is undefined and is **guarded by a 1. If the element at index `idx` does not exist, the behavior is undefined.
[runtime assertion](../../features/assertions.md)**!
2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a 2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**! [runtime assertion](../../features/assertions.md)**!
3. If the JSON pointer `ptr` refers to an object key or an array index that does not exist, the behavior is
undefined and is **guarded by a [runtime assertion](../../features/assertions.md)**!
1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the 1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the
array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In
@@ -267,11 +261,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
## Version history ## Version history
1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and 1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and
accessing it out of bounds when `idx` equals the maximum value of `size_type`. A missing index in the const version accessing it out of bounds when `idx` equals the maximum value of `size_type`.
is guarded by a runtime assertion since version 3.13.0.
2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3) 2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3)
in version 3.11.0. 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 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 [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions. already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions.
4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since 4. Added in version 2.0.0.
version 3.13.0.
+3 -1
View File
@@ -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 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 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. 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 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 `ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or
`default_value`, as documented). `default_value`, as documented).
+7 -31
View File
@@ -16,15 +16,14 @@ before including the `json.hpp` header.
## Function with runtime assertions ## Function with runtime assertions
### Unchecked access to a const value ### Unchecked object access to a const value
Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for arrays and objects. Whereas Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for objects. Whereas a missing
a missing element is added in the case of non-const values, accessing a const value with a missing object key or an key is added in the case of non-const objects, accessing a const object with a missing key is undefined behavior (think
invalid array index is undefined behavior (think of a dereferenced null pointer) and yields a runtime assertion. This of a dereferenced null pointer) and yields a runtime assertion.
also applies to a [JSON pointer](json_pointer.md) that refers to a missing key or an invalid index.
If you are not sure whether an element exists, use checked access with the [`at` function](../api/basic_json/at.md) If you are not sure whether an element in an object exists, use checked access with the
or call the [`contains` function](../api/basic_json/contains.md) before. [`at` function](../api/basic_json/at.md) or call the [`contains` function](../api/basic_json/contains.md) before.
See also the documentation on [element access](element_access/index.md). See also the documentation on [element access](element_access/index.md).
@@ -47,30 +46,7 @@ See also the documentation on [element access](element_access/index.md).
Output: Output:
``` ```
Assertion failed: (it != m_data.m_value.object->end()), function operator[], file json.hpp, line 28795. Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144.
```
??? example "Example 2: Invalid array index in a JSON pointer"
The following code will trigger an assertion at runtime:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace nlohmann::literals;
int main()
{
const json j = {{"array", {1, 2, 3}}};
auto v = j["/array/5"_json_pointer];
}
```
Output:
```
Assertion failed: (idx < m_data.m_value.array->size()), function operator[], file json.hpp, line 28758.
``` ```
### Constructing from an uninitialized iterator range ### Constructing from an uninitialized iterator range
+2 -8
View File
@@ -536,10 +536,6 @@ class json_pointer
@return const reference to the JSON value pointed to by the JSON @return const reference to the JSON value pointed to by the JSON
pointer pointer
@pre Every object key and array index the pointer refers to exists.
Like the const operator[] for keys and indices, a missing one is
undefined behavior, guarded by a runtime assertion.
@throw parse_error.106 if an array index begins with '0' @throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number @throw parse_error.109 if an array index was not a number
@throw out_of_range.402 if the array index '-' is used @throw out_of_range.402 if the array index '-' is used
@@ -554,8 +550,7 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
// use unchecked object access; the const operator[] // use unchecked object access
// asserts that the key exists
ptr = &ptr->operator[](reference_token); ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -568,8 +563,7 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
} }
// use unchecked array access; the const operator[] // use unchecked array access
// asserts that the index exists
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
break; break;
} }
+25 -3
View File
@@ -748,6 +748,30 @@ using is_usable_as_key_type = typename std::conditional <
std::true_type, std::true_type,
std::false_type >::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 // type trait to check if KeyType can be used as an object key
// true if: // true if:
// - KeyType is comparable with BasicJsonType::object_t::key_type // - KeyType is comparable with BasicJsonType::object_t::key_type
@@ -761,9 +785,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional <
typename BasicJsonType::object_t::key_type, KeyTypeCVRef, typename BasicJsonType::object_t::key_type, KeyTypeCVRef,
RequireTransparentComparator, ExcludeObjectKeyType>::value RequireTransparentComparator, ExcludeObjectKeyType>::value
&& !is_json_iterator_of<BasicJsonType, KeyType>::value) && !is_json_iterator_of<BasicJsonType, KeyType>::value)
#ifdef JSON_HAS_CPP_17 || is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef>::value
|| std::is_convertible<KeyType, std::string_view>::value
#endif
, std::true_type, , std::true_type,
std::false_type >::type; std::false_type >::type;
+32 -13
View File
@@ -807,6 +807,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return it; 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 /// @brief erase an element from the object and return the following one
/// Not every map returns an iterator from erase(iterator): some containers /// Not every map returns an iterator from erase(iterator): some containers
/// (e.g., Abseil's hash maps) return void to avoid computing a successor /// (e.g., Abseil's hash maps) return void to avoid computing a successor
@@ -2767,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)); 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()) 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)); JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
@@ -2805,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)); 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()) 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)); JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
@@ -2874,7 +2892,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for arrays // const operator[] only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
JSON_ASSERT(idx < m_data.m_value.array->size());
return m_data.m_value.array->operator[](idx); return m_data.m_value.array->operator[](idx);
} }
@@ -2949,7 +2966,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// operator[] only works for objects // operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object())) 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); return set_parent(result.first->second);
} }
@@ -2965,7 +2982,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for objects // const operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object())) 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()); JSON_ASSERT(it != m_data.m_value.object->end());
return it->second; return it->second;
} }
@@ -2975,8 +2992,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
private: private:
template<typename KeyType> template<typename KeyType>
using is_comparable_with_object_key = detail::is_comparable < using is_comparable_with_object_key = std::integral_constant < bool,
object_comparator_t, const typename object_t::key_type&, KeyType >; 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> template<typename ValueType>
using value_return_type = std::conditional < using value_return_type = std::conditional <
@@ -3363,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)); 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()) if (it != m_data.m_value.object->end())
{ {
m_data.m_value.object->erase(it); m_data.m_value.object->erase(it);
@@ -3390,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> detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
size_type erase(KeyType && key) 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 /// @brief remove element from a JSON array given an index
@@ -3470,7 +3489,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_object()) 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; return result;
@@ -3486,7 +3505,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_object()) 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; return result;
@@ -3509,7 +3528,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
size_type count(KeyType && key) const size_type count(KeyType && key) const
{ {
// return 0 for all nonobject types // 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 /// @brief check the existence of an element in a JSON object
@@ -3527,7 +3546,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(KeyType && key) const 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 /// @brief check the existence of an element in a JSON object given a JSON pointer
+59 -24
View File
@@ -4756,6 +4756,30 @@ using is_usable_as_key_type = typename std::conditional <
std::true_type, std::true_type,
std::false_type >::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 // type trait to check if KeyType can be used as an object key
// true if: // true if:
// - KeyType is comparable with BasicJsonType::object_t::key_type // - KeyType is comparable with BasicJsonType::object_t::key_type
@@ -4769,9 +4793,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional <
typename BasicJsonType::object_t::key_type, KeyTypeCVRef, typename BasicJsonType::object_t::key_type, KeyTypeCVRef,
RequireTransparentComparator, ExcludeObjectKeyType>::value RequireTransparentComparator, ExcludeObjectKeyType>::value
&& !is_json_iterator_of<BasicJsonType, KeyType>::value) && !is_json_iterator_of<BasicJsonType, KeyType>::value)
#ifdef JSON_HAS_CPP_17 || is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef>::value
|| std::is_convertible<KeyType, std::string_view>::value
#endif
, std::true_type, , std::true_type,
std::false_type >::type; std::false_type >::type;
@@ -20148,10 +20170,6 @@ class json_pointer
@return const reference to the JSON value pointed to by the JSON @return const reference to the JSON value pointed to by the JSON
pointer pointer
@pre Every object key and array index the pointer refers to exists.
Like the const operator[] for keys and indices, a missing one is
undefined behavior, guarded by a runtime assertion.
@throw parse_error.106 if an array index begins with '0' @throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number @throw parse_error.109 if an array index was not a number
@throw out_of_range.402 if the array index '-' is used @throw out_of_range.402 if the array index '-' is used
@@ -20166,8 +20184,7 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
// use unchecked object access; the const operator[] // use unchecked object access
// asserts that the key exists
ptr = &ptr->operator[](reference_token); ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -20180,8 +20197,7 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
} }
// use unchecked array access; the const operator[] // use unchecked array access
// asserts that the index exists
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
break; break;
} }
@@ -27740,6 +27756,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return it; 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 /// @brief erase an element from the object and return the following one
/// Not every map returns an iterator from erase(iterator): some containers /// Not every map returns an iterator from erase(iterator): some containers
/// (e.g., Abseil's hash maps) return void to avoid computing a successor /// (e.g., Abseil's hash maps) return void to avoid computing a successor
@@ -29700,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)); 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()) 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)); JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
@@ -29738,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)); 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()) 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)); JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this));
@@ -29807,7 +29841,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for arrays // const operator[] only works for arrays
if (JSON_HEDLEY_LIKELY(is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
JSON_ASSERT(idx < m_data.m_value.array->size());
return m_data.m_value.array->operator[](idx); return m_data.m_value.array->operator[](idx);
} }
@@ -29882,7 +29915,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// operator[] only works for objects // operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object())) 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); return set_parent(result.first->second);
} }
@@ -29898,7 +29931,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// const operator[] only works for objects // const operator[] only works for objects
if (JSON_HEDLEY_LIKELY(is_object())) 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()); JSON_ASSERT(it != m_data.m_value.object->end());
return it->second; return it->second;
} }
@@ -29908,8 +29941,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
private: private:
template<typename KeyType> template<typename KeyType>
using is_comparable_with_object_key = detail::is_comparable < using is_comparable_with_object_key = std::integral_constant < bool,
object_comparator_t, const typename object_t::key_type&, KeyType >; 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> template<typename ValueType>
using value_return_type = std::conditional < using value_return_type = std::conditional <
@@ -30296,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)); 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()) if (it != m_data.m_value.object->end())
{ {
m_data.m_value.object->erase(it); m_data.m_value.object->erase(it);
@@ -30323,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> detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
size_type erase(KeyType && key) 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 /// @brief remove element from a JSON array given an index
@@ -30403,7 +30438,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_object()) 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; return result;
@@ -30419,7 +30454,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_object()) 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; return result;
@@ -30442,7 +30477,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
size_type count(KeyType && key) const size_type count(KeyType && key) const
{ {
// return 0 for all nonobject types // 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 /// @brief check the existence of an element in a JSON object
@@ -30460,7 +30495,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(KeyType && key) const 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 /// @brief check the existence of an element in a JSON object given a JSON pointer
+111
View File
@@ -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 #endif