json icon indicating copy to clipboard operation
json copied to clipboard

Constructing array from C++20 ranges view does not work

Open stevenwdv opened this issue 3 months ago • 8 comments

Description

Constructing a json object from a C++20 view (e.g. std::ranges::transform_view/std::ranges::filter_view) does not work.

Reproduction steps

Constructing a json object from a std::vector works fine, but constructing from a more basic range such as a vector passed through std::views::filter does not work. This holds for a range of json object as well as a range of plain ints.

Expected vs. actual results

Expected: json value constructs as array with elements from the view (e.g. below: with contents [37, 42, 21]).

Actual: Fails to compile as is_compatible_array_type returns false for some reason.

Minimal code example

#include <iostream>
#include <ranges>
#include <vector>

#include <nlohmann/json.hpp>

int main() {
    std::vector<int> nums{1, 2, 37, 42, 21};
    auto filteredNums = nums | std::views::filter([](int i) { return i > 10; });
    nlohmann::json j(filteredNums);
    std::cout << j << std::endl;
}

Repro on Compiler Explorer.

Error messages

<source>:10:20: error: no matching constructor for initialization of 'nlohmann::json' (aka 'basic_json<>')
   10 |     nlohmann::json j(filteredNums);
      |                    ^ ~~~~~~~~~~~~
[...]
/opt/compiler-explorer/libs/nlohmann_json/trunk/single_include/nlohmann/json.hpp:20942:5: note: candidate template ignored: requirement 'nlohmann::detail::is_compatible_type<nlohmann::basic_json<std::map, std::vector, std::basic_string<char, std::char_traits<char>, std::allocator<char>>, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char>>, void>, std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int>>>, (lambda at <source>:9:51)>>::value' was not satisfied [with CompatibleType = typename __invoke_result<const std::ranges::views::_Filter &, std::vector<int, std::allocator<int>> &, const (lambda at <source>:9:51) &>::type &, U = detail::uncvref_t<typename __invoke_result<const std::ranges::views::_Filter &, std::vector<int, std::allocator<int>> &, const (lambda at <source>:9:51) &>::type &>]
 20942 |     basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
       |     ^
[...]

Compiler and operating system

Clang 22 / GCC 16

Library version

3.11.3, 3.12.0

Validation

stevenwdv avatar Sep 11 '25 10:09 stevenwdv

@stevenwdv You didn't check the "The bug also occurs if the latest version from the develop branch is used." box.

Does that mean it doesn't happen anymore, or did you just not try it?

gregmarr avatar Sep 11 '25 14:09 gregmarr

@gregmarr I didn't try, although I don't know what version Compiler Explorer has. Do you want me to try?

stevenwdv avatar Sep 11 '25 17:09 stevenwdv

The 3.11.3 version is almost two years old, should at least confirm in 3.12.0 which is from April.

gregmarr avatar Sep 11 '25 19:09 gregmarr

@gregmarr Ah, sorry, apparently the latest version was kept back in our repo by a transitive dependency specifying an older version. Unfortunately, the same error still occurs in 3.12.0:

/home/swdv/Downloads/tst/test.cpp:10:20: error: no matching constructor for initialization of 'nlohmann::json'
      (aka 'basic_json<>')
   10 |     nlohmann::json j(filteredNums);
[...]
/home/swdv/.conan2/p/nlohmd014ef7748f4b/p/include/nlohmann/json.hpp:838:5: note: candidate template ignored:
      requirement 'nlohmann::detail::is_compatible_type<nlohmann::basic_json<std::map, std::vector, std::basic_string<char,
      std::char_traits<char>, std::allocator<char>>, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer,
      std::vector<unsigned char, std::allocator<unsigned char>>, void>,
      std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int>>>, (lambda at
      /home/swdv/Downloads/tst/test.cpp:9:51)>>::value' was not satisfied [with CompatibleType =
      filter_view<views::all_t<std::vector<int, std::allocator<int>> &>, (lambda at /home/swdv/Downloads/tst/test.cpp:9:51)> &, U =
      detail::uncvref_t<filter_view<views::all_t<std::vector<int, std::allocator<int>> &>, (lambda at
      /home/swdv/Downloads/tst/test.cpp:9:51)> &>]
  838 |     basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
      |     ^
[...]

stevenwdv avatar Sep 12 '25 07:09 stevenwdv

As far as I can tell, the current version of nlohmann/json does not yet provide a constructor overload that accepts C++20 range views (like std::ranges::filter_view or std::ranges::transform_view).

A practical workaround is to convert the view to a container first. For example, in C++23:

auto filtered = nums | std::views::filter([](int i) { return i > 10; }) | std::ranges::tostd::vector();

nlohmann::json j(filtered);

CrysisKiller avatar Sep 29 '25 11:09 CrysisKiller

@CrysisKiller I'm not sure, because it really seems like they intended to support a broader set of ranges if you look at is_compatible_array_type, but somewhere some check fails. But yeah, first creating a vector would be a workaround.

stevenwdv avatar Sep 29 '25 11:09 stevenwdv

@stevenwdv I tried checking nlohmann::detail::is_compatible_array_type for the type of filteredNums to see whether it’s considered a valid argument for the JSON constructor. It evaluates to false, which explains why the constructor is not selected.

Example:

std::vector nums{1, 2, 37, 42, 21}; auto filteredNums = nums | std::views::filter([](int i) { return i > 10; });

using ViewType = decltype(filteredNums); constexpr bool isSupported = nlohmann::detail::is_compatible_array_type<nlohmann::json, ViewType>::value;

std::cout << std::boolalpha << "is_compatible_array_type for filter_view: " << isSupported << "\n";

Image

CrysisKiller avatar Sep 29 '25 12:09 CrysisKiller

I found some workaround by specializing is_compatible_array_type and wrapping the filter_view into ref_view and then it worked. the solution is not the best but I checked the other traits and I see the nlohmann::detail::is_range<R>::is_iterator_begin evaluated to false for views and I checked the nlohmann::detail::is_iterator_traits, tries to detect value_type_t, difference_type_t, pointer_t, iterator_category_t, reference_t typedefs and std::views doesn't have some or all of them (not sure about it).

workaround

mguludag avatar Sep 30 '25 12:09 mguludag