json icon indicating copy to clipboard operation
json copied to clipboard

Enable string_view keys in operator[] by updating is_usable_as_basic_json_key_type (#3912)

Open khloodelhossiny opened this issue 2 months ago • 19 comments

This pull request adds std::string_view support for operator[] to improve usability with modern C++ (C++17 and later). The behavior now matches the existing std::string overloads. Additionally, the exception messages were updated for consistency when operator[] is used with non-object types and a string_view key.

This change addresses and fixes issue #3912.

  • [x] The changes are described in detail, both the what and why.
  • [x] An existing issue is referenced (Fixes #3912).
  • [x] The Code coverage remains at 100%.. A test case for every new line of code.
  • [ ] If applicable, the documentation is updated.
  • [x] The source code is amalgamated by running make amalgamate.

Read the Contribution Guidelines for detailed information.

khloodelhossiny avatar Oct 22 '25 12:10 khloodelhossiny

string_view support already exists through this function:

    /// @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_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >
    reference operator[](KeyType && key)

https://github.com/nlohmann/json/pull/3423

As you can see from the godbolt link in the original issue, the issue isn't that string_view doesn't work, it's that the compiler doesn't pick up that the operator string_view() from the type should allow it to work.

The thing that should really be updated is is_usable_as_basic_json_key_type, and then all of the existing functions will work. I think it currently asks "can this type be used as a key" when this scenario needs it to ask "can this type be converted to a type that can be used as a key".

You can update the test code like this to see the missing piece.

	std::cout << js[static_cast<std::string_view>(Bar{})] << "\n";  // Okay
	std::cout << js[Bar{}] << "\n"; // Not okay

gregmarr avatar Oct 22 '25 16:10 gregmarr

@gregmarr Yes, I understand that string_view is supported through the existing template function, and the issue arises with js[Bar{}] due to ambiguity between the two overloads, causing the compiler to fail.

My idea is to add specific string_view overloads for operator[] to guide the compiler in choosing the correct one, resolving the ambiguity.

khloodelhossiny avatar Oct 22 '25 17:10 khloodelhossiny

🔴 Amalgamation check failed! 🔴

The source code has not been amalgamated. @khloodelhossiny Please read and follow the Contribution Guidelines.

github-actions[bot] avatar Oct 22 '25 17:10 github-actions[bot]

My idea is to add specific string_view overloads for operator[] to guide the compiler in choosing the correct one, resolving the ambiguity.

Okay, that's not what your PR description says, so it wasn't clear that you knew that.

I'd be worried about the compiler potentially having a problem selecting between operator[](KeyType && key) and operator[](std::string_view key) and there also being unnecessary duplication. I still think that making is_usable_as_basic_json_key_type work with it is a better route here, but @nlohmann may be okay with the new functions.

gregmarr avatar Oct 23 '25 01:10 gregmarr

I think a cleaner approach would be indeed to fix is_usable_as_basic_json_key_type to also work with string views.

nlohmann avatar Oct 23 '25 07:10 nlohmann

My idea is to add specific string_view overloads for operator[] to guide the compiler in choosing the correct one, resolving the ambiguity.

Okay, that's not what your PR description says, so it wasn't clear that you knew that.

I'd be worried about the compiler potentially having a problem selecting between operator[](KeyType && key) and operator[](std::string_view key) and there also being unnecessary duplication. I still think that making is_usable_as_basic_json_key_type work with it is a better route here, but @nlohmann may be okay with the new functions.

Thanks a lot for the explanation. I see your point, and I’ll update the code to follow that approach.

khloodelhossiny avatar Oct 23 '25 10:10 khloodelhossiny

I think a cleaner approach would be indeed to fix is_usable_as_basic_json_key_type to also work with string views.

Thanks for the feedback! Yes, that makes sense — I will update the code.

khloodelhossiny avatar Oct 23 '25 10:10 khloodelhossiny

🔴 Amalgamation check failed! 🔴

The source code has not been amalgamated. @khloodelhossiny Please read and follow the Contribution Guidelines.

github-actions[bot] avatar Oct 25 '25 05:10 github-actions[bot]

Coverage Status

coverage: 99.191%. remained the same when pulling 6d975144a57e44986d49b4ac5fdc6378463bd870 on khloodelhossiny:fix_issue-3912 into c8b66cf36e9e00516fd8cebaf15787ec031f5ae0 on nlohmann:develop.

coveralls avatar Oct 25 '25 06:10 coveralls

🔴 Amalgamation check failed! 🔴

The source code has not been amalgamated. @khloodelhossiny Please read and follow the Contribution Guidelines.

github-actions[bot] avatar Oct 27 '25 13:10 github-actions[bot]

🔴 Amalgamation check failed! 🔴

The source code has not been amalgamated. @khloodelhossiny Please read and follow the Contribution Guidelines.

github-actions[bot] avatar Oct 29 '25 13:10 github-actions[bot]

I was hoping for something that didn't have to name string_view directly, but I'm not sure that's possible.

Another possibility that seems to work and doesn't require any changes to the library is to make your type comparable to std::string.

struct Bar {
	operator std::string_view() const { return "bar"; }
};
bool operator<(const std::string &lhs, Bar const &rhs) {
    return lhs < static_cast<std::string_view>(rhs);
}

It could be a bit more generic, but that might be dangerous in the rest of the program:

template<typename T>
bool operator<(const std::string &lhs, T &&rhs) {
    return lhs < static_cast<std::string_view>(rhs);
}
template<typename T>
bool operator<(T&&lhs, const std::string &rhs) {
    return static_cast<std::string_view>(lhs) < rhs;
}

gregmarr avatar Oct 31 '25 23:10 gregmarr

I was hoping for something that didn't have to name string_view directly, but I'm not sure that's possible.

Another possibility that seems to work and doesn't require any changes to the library is to make your type comparable to std::string.

struct Bar {
	operator std::string_view() const { return "bar"; }
};
bool operator<(const std::string &lhs, Bar const &rhs) {
    return lhs < static_cast<std::string_view>(rhs);
}

It could be a bit more generic, but that might be dangerous in the rest of the program:

template<typename T>
bool operator<(const std::string &lhs, T &&rhs) {
    return lhs < static_cast<std::string_view>(rhs);
}
template<typename T>
bool operator<(T&&lhs, const std::string &rhs) {
    return static_cast<std::string_view>(lhs) < rhs;
}

I got your point — I understand that you want the library to fully support std::string_view. For example, this code:

struct TestStruct {
    operator std::string_view() const { return "key"; }
};
std::cout << j.contains(TestStruct{}) << "\n";

I tried to handle that case, but I think for now it might make sense to let users define their own comparison operators if they have custom types. At least we’ve fixed the issue that many people usually miss. I also agree that in the future, we should aim to support full std::string_view handling as a proper feature of the library.

khloodelhossiny avatar Nov 01 '25 14:11 khloodelhossiny

@gregmarr Can you help me? I’m having some trouble with the CI checks. Every time I push, I get these errors:

  • Ubuntu / ci_static_analysis_clang (ci_test_clang)
  • Ubuntu / ci_test_standards_clang (17, libstdcxx)
  • Windows / mingw (x64)

I tried to fix them several times, but after I push new changes, other errors appear.

I believe most of them are related to clang-format. When I run the formatting command locally, it changes macros that should stay on one line into multiple lines, which then causes conflicts.

My current steps are:

  1. Edit the two files
  2. Run the tests
  3. Run make amalgamate
  4. Push

If I’m missing any step, please let me know — I’m starting to get a bit disappointed :(

At this point, I almost believe that even if I push the develop branch, it will still fail the same checks :)

khloodelhossiny avatar Nov 01 '25 14:11 khloodelhossiny

@nlohmann will have to help you with the checks.

I also agree that in the future, we should aim to support full std::string_view handling as a proper feature of the library.

IMO, the library supports std::string_view as a proper feature already. The issue here is that the object isn't directly usable as a key but requires an intermediate conversion to a type that is usable as a key.

gregmarr avatar Nov 03 '25 00:11 gregmarr

nlohmann avatar Nov 03 '25 09:11 nlohmann

IMO, the library supports std::string_view as a proper feature already. The issue here is that the object isn't directly usable as a key but requires an intermediate conversion to a type that is usable as a key.

Got it, thank you so much for the clarification and guidance! What I actually meant was that it doesn’t fully support types that can be implicitly converted to std::string_view. But yes, I’ll try to find the best solution for that.

khloodelhossiny avatar Nov 03 '25 12:11 khloodelhossiny

@nlohmann Many thanks to you!

khloodelhossiny avatar Nov 03 '25 12:11 khloodelhossiny

Hi @nlohmann , I've resolved the conflicts and updated the branch. All CI checks should be green now. Let me know if there's anything else needed! Thanks for the feedback

khloodelhossiny avatar Dec 03 '25 11:12 khloodelhossiny