Skip to content

contains(json_pointer) and json_pointer / size_t fail to compile with a conforming custom StringType #5666

Closed

Description

@nlohmann

Description

With a custom StringType that provides exactly the members the documentation lists as required, basic_json::contains(const json_pointer&) and json_pointer::operator/(std::size_t) / operator/=(std::size_t) fail to compile, because they use operations on string_t that are not in the documented requirements:

  1. include/nlohmann/detail/json_pointer.hpp#L749 (in json_pointer::contains): "0" <= reference_token && reference_token <= "9" requires operator<= between const char[2] and string_t.
  2. include/nlohmann/detail/json_pointer.hpp#L777 (in json_pointer::contains): std::strtoull(reference_token.c_str(), ...) requires c_str().
  3. include/nlohmann/detail/json_pointer.hpp#L119 (operator/=(std::size_t), also used by operator/(const json_pointer&, std::size_t)): *this /= std::to_string(array_idx) requires string_t to be constructible from std::string.

The template parameter documentation states that "c_str() and back() are not required", lists only == and < as required comparisons, and says a std::size_t is converted either via assignment from std::to_string or via an ADL int_to_string (which the program below provides). at(json_pointer) compiles with the same type.

Since when: (1) and (3) date from 2020 (8bfc692, 78e911b) and fail with v3.12.0 as well; (2) was added in #5495 (8e26b9d, 2026-09-09). The requirements were written down in #5443 (c72f37a, 2026-09-15), which added tests/src/unit-alt-string.cpp coverage for other JSON pointer functions but not for these two. With the v3.12.0 headers the program fails with many more errors (for example c_str() in the serializer), so this is not a regression relative to a release.

Separately, the contains(ptr) call emits two -Wdeprecated-declarations warnings ("'operator my_string' is deprecated") from detail::is_comparable evaluating std::less<> between my_string and json_pointer for the contains(KeyType&&) overload; my_string's non-template operator< makes the deprecated json_pointer::operator string_t() viable. This is similar to #4621 (fixed for ordered_json) and is not changed by the fix below.

Related but different: #3388 and #4019 (earlier compile errors of json_pointer and flatten() with alternative string types, both fixed).

The documentation's own reference implementation, custom_string_type.hpp, which the StringType section presents as a type that satisfies every requirement, fails the same way for contains(json_pointer) (the c_str() and "0" <= token errors). The example's own gaps (no way to append a char, needed by json_pointer::to_string(), flatten(), unflatten(), and diff()) are fixed in #5638; contains(json_pointer) still fails with the fixed example, because the cause is in the library.

Possible fix: use only documented members. I tested the following with the program below (C++11 and C++17) and with a quick std::string check of contains() and operator/; I did not run the test suite:

// json_pointer.hpp:119
return *this /= detail::to_string<string_t>(array_idx);   // needs #include <nlohmann/detail/string_utils.hpp>
// json_pointer.hpp:749
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !('0' <= reference_token[0] && reference_token[0] <= '9')))
// json_pointer.hpp:777 (data() is documented to be null-terminated)
const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10);

Adding contains(json_pointer) and operator/(std::size_t) to tests/src/unit-alt-string.cpp would catch this.

Reproduction steps

  1. Save the program below as c07.cpp.
  2. Compile it against develop: clang++ -std=c++17 -I include c07.cpp -o c07 (GCC 16.2 reports the same three errors).

Expected vs. actual results

  • Expected: the program compiles and prints 1, 1, 2.
  • Actual: three compile errors in json_pointer.hpp; without the contains and operator/ lines the program compiles and runs.

Minimal code example

#include <nlohmann/json.hpp>
#include <cstddef>
#include <iostream>
#include <map>
#include <string>
#include <vector>

// a string type with the members docs/mkdocs/docs/features/types/template_parameters.md
// lists as required (no c_str(), no comparison with const char*, not constructible from std::string)
class my_string
{
  public:
    using value_type = char;
    using size_type = std::size_t;
    using iterator = std::string::iterator;
    using const_iterator = std::string::const_iterator;
    static constexpr size_type npos = std::string::npos;

    my_string() = default;
    my_string(const char* s) : s_(s) {}
    my_string(const char* s, size_type n) : s_(s, n) {}
    my_string(size_type n, char c) : s_(n, c) {}

    size_type size() const noexcept { return s_.size(); }
    bool empty() const noexcept { return s_.empty(); }
    void clear() noexcept { s_.clear(); }
    void reserve(size_type n) { s_.reserve(n); }
    void resize(size_type n) { s_.resize(n); }
    void resize(size_type n, char c) { s_.resize(n, c); }
    const char* data() const noexcept { return s_.data(); }
    void push_back(char c) { s_.push_back(c); }
    char& operator[](size_type i) { return s_[i]; }
    const char& operator[](size_type i) const { return s_[i]; }
    my_string& append(const char* s, size_type n) { s_.append(s, n); return *this; }
    my_string& append(const my_string& o) { s_.append(o.s_); return *this; }
    size_type find_first_of(char c, size_type pos = 0) const { return s_.find_first_of(c, pos); }
    iterator begin() { return s_.begin(); }
    iterator end() { return s_.end(); }
    const_iterator begin() const { return s_.begin(); }
    const_iterator end() const { return s_.end(); }

    friend bool operator==(const my_string& a, const my_string& b) { return a.s_ == b.s_; }
    friend bool operator<(const my_string& a, const my_string& b) { return a.s_ < b.s_; }
    friend std::ostream& operator<<(std::ostream& o, const my_string& s) { return o << s.s_; }

  private:
    std::string s_;
};

void int_to_string(my_string& target, std::size_t value)
{
    target = std::to_string(value).c_str();
}

using my_json = nlohmann::basic_json<std::map, std::vector, my_string>;

int main()
{
    my_json j = {{"foo", {1, 2}}};
    const my_json::json_pointer ptr("/foo/0");

    std::cout << j.at(ptr) << '\n';        // compiles

    std::cout << j.contains(ptr) << '\n';  // expected: compiles, prints 1
                                           // actual:   error: no member named 'c_str' in 'my_string'
                                           //           error: invalid operands ('const char[2]' and 'const my_string')

    auto p = my_json::json_pointer("/foo") / std::size_t(1);  // expected: compiles
                                                              // actual:   error: no viable overloaded '/='
    std::cout << j.at(p) << '\n';
}

Error messages

Trimmed to the errors and their first instantiation notes:

include/nlohmann/detail/json_pointer.hpp:749:83: error: invalid operands to binary expression ('const char[2]' and 'const my_string')
  749 |                     if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
      |                                                                               ~~~ ^  ~~~~~~~~~~~~~~~
include/nlohmann/json.hpp:3515:20: note: in instantiation of function template specialization 'nlohmann::json_pointer<my_string>::contains<nlohmann::basic_json<std::map, std::vector, my_string>>' requested here
c07.cpp:64:20: note: in instantiation of member function 'nlohmann::basic_json<std::map, std::vector, my_string>::contains' requested here
include/nlohmann/detail/json_pointer.hpp:777:88: error: no member named 'c_str' in 'my_string'
  777 |                     const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
      |                                                                        ~~~~~~~~~~~~~~~ ^
include/nlohmann/detail/json_pointer.hpp:119:22: error: no viable overloaded '/='
  119 |         return *this /= std::to_string(array_idx);
      |                ~~~~~ ^  ~~~~~~~~~~~~~~~~~~~~~~~~~
include/nlohmann/detail/json_pointer.hpp:141:34: note: in instantiation of member function 'nlohmann::json_pointer<my_string>::operator/=' requested here
c07.cpp:68:44: note: in instantiation of member function 'nlohmann::operator/' requested here
2 warnings and 3 errors generated.

Compiler and operating system

Apple clang 21.0.0 (clang-2100.3.34.2), macOS 27.0 (arm64); also GCC 16.2 (/opt/homebrew/bin/g++-16)

Library version

develop @ 633de8e

Validation

  • The bug also occurs if the latest version from the develop branch is used.
  • I can successfully compile and run the unit tests.

This issue was written by Claude Code on behalf of @nlohmann.

Activity

  1. self-assigned this
    on Sep 29, 2026
  2. added this to the Release 3.13.0 milestone on Sep 30, 2026
  3. added a commit that references this issue on Oct 1, 2026
    fdcc569
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

kind: bugsolution: proposed fixa fix for the issue has been proposed and waits for confirmation

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions