Skip to content

fix: support constructing json from C++20 range views (#4916)#5205

Open
federicosfriso05-dotcom wants to merge 4 commits into
nlohmann:developfrom
federicosfriso05-dotcom:fix/issue-4916-ranges-view-support
Open

fix: support constructing json from C++20 range views (#4916)#5205
federicosfriso05-dotcom wants to merge 4 commits into
nlohmann:developfrom
federicosfriso05-dotcom:fix/issue-4916-ranges-view-support

Conversation

@federicosfriso05-dotcom

Copy link
Copy Markdown

Description

Fixes #4916.

Constructing a nlohmann::json object from a C++20 range view (e.g. std::views::filter, std::views::transform) failed to compile because is_compatible_array_type did not recognize range views as compatible types.

What changed

  • include/nlohmann/detail/meta/type_traits.hpp: added a new partial specialization of is_compatible_array_type_impl for C++20 range views, guarded by #ifdef JSON_HAS_CPP_20. The specialization matches types satisfying std::ranges::view while excluding character sequences (e.g. std::string_view).
  • include/nlohmann/detail/conversions/to_json.hpp: added a new construct overload for C++20 range views inside external_constructor<value_t::array>, also guarded by #ifdef JSON_HAS_CPP_20.
  • The specialization uses std::ranges::view rather than just std::ranges::range to avoid ambiguity with standard containers like std::string and std::string_view, which already satisfy std::ranges::range but are handled by existing specializations. Character sequences are additionally excluded by checking that the range's value type is not char or wchar_t.

How to test

std::vector<int> nums{1, 2, 37, 42, 21};
auto filtered = nums | std::views::filter([](int i) { return i > 10; });
nlohmann::json j(filtered);
assert(j == nlohmann::json({37, 42, 21}));
  • The changes are described in detail, both the what and why.
  • If applicable, an existing issue is referenced.
  • The Code coverage remained at 100%. A test case for every new line of code.
  • If applicable, the documentation is updated.
  • The source code is amalgamated by running make amalgamate.

@federicosfriso05-dotcom

Copy link
Copy Markdown
Author

Hi, I noticed the Windows/MinGW builds are failing. After investigating the CI logs, the issue seems to be related to an incomplete C++20 ranges implementation in MinGW GCC 12.2.0, where std::ranges::ref_view and std::ranges::filter_view do not work correctly on that toolchain. The same code compiles and runs correctly on GCC/Linux and Clang/Mac.
Should the test be guarded against MinGW with something like #ifndef __MINGW32__, or is there a different approach you'd recommend for handling this compiler limitation?
Thanks!

@nlohmann

nlohmann commented Jun 8, 2026

Copy link
Copy Markdown
Owner

IIRC we also excluded some other older compilers from other C++20 features. So I guess and ifdef with some specific versions and comments would be sufficient.

@federicosfriso05-dotcom

Copy link
Copy Markdown
Author

Updated. Replaced JSON_HAS_CPP_20 with JSON_HAS_RANGES && !defined(__MINGW32__) as suggested, following the existing pattern in macro_scope.hpp

@federicosfriso05-dotcom

Copy link
Copy Markdown
Author

I noticed there's still a failure on GCC 11 in unit-iterators2.cppiteration_proxy, an internal nlohmann type, appears to satisfy std::ranges::view, causing a circular constraint evaluation error. I'm not sure how to exclude internal types cleanly without knowing the codebase better. Any guidance on the right approach would be appreciated

@federicosfriso05-dotcom

Copy link
Copy Markdown
Author

Hi, just checking in after the CI failures. I understand the issue with iteration_proxy satisfying std::ranges::view — happy to implement any fix you suggest for handling internal types correctly.

@nlohmann nlohmann added the please rebase Please rebase your branch to origin/develop label Jun 30, 2026
@nlohmann

Copy link
Copy Markdown
Owner

Now that #5161 has merged, a few things:

  1. Please rebase on develop: it now conflicts (we touched the same JSON_HAS_RANGES/type_traits.hpp areas). After rebasing, your Constructing array from C++20 ranges view does not work #4916 regression test will sit next to the Assert when using std::views::filter and GCC 10 #4440 one Harden JSON_HAS_RANGES detection for incomplete C++20 ranges implementations #5161 added; please reconcile so we don't duplicate.

  2. Guard the new #include <ranges> in unit-regression2.cpp: it's currently unconditional and breaks every pre-C++20 build (GCC 5, Clang 3.x: ranges: No such file or directory). Wrap it the way Harden JSON_HAS_RANGES detection for incomplete C++20 ranges implementations #5161 does: #if JSON_HAS_RANGES.

  3. Keep the !defined(__MINGW32__) guards: develop's ranges detection still doesn't special-case MinGW, so they're needed.

  4. The remaining real blocker is GCC 11. The failure is on std::ranges::ref_view<basic_json> - a view over basic_json itself - which trips libstdc++ 11's self-referential swappable/movable constraint (atomic constraint depends on itself). #5161 doesn't cover this (it only disables ranges through GCC 10). Simplest fix: gate the view specialization off on libstdc++ 11, e.g. add _GLIBCXX_RELEASE >= 12 to the #if. Excluding basic_json-valued views by range_value_t would be cleaner conceptually, but may not help here since GCC recurses while merely evaluating std::ranges::view<ref_view<basic_json>> — so the version guard is the safe bet.

Signed-off-by: Federico Sfriso <federicosfriso05@gmail.com>
Signed-off-by: Federico Sfriso <federicosfriso05@gmail.com>
…l ranges include

Signed-off-by: Federico Sfriso <federicosfriso05@gmail.com>
@federicosfriso05-dotcom federicosfriso05-dotcom force-pushed the fix/issue-4916-ranges-view-support branch from 113af6b to 60ecc77 Compare June 30, 2026 14:14
@nlohmann nlohmann removed the please rebase Please rebase your branch to origin/develop label Jun 30, 2026
@federicosfriso05-dotcom

Copy link
Copy Markdown
Author

I've rebased and applied all 4 fixes you suggested. Given the ci_test_compilers_gcc (12) failure, I guess that the _GLIBCXX_RELEASE >= 12 does not fully solve the problem. Is there a more specific way to exclude basic_json-valued views specifically?
Then, looking at the library code, the library guard (!defined(__MINGW32__)) correctly excludes the feature, but tests/src/unit-regression2.cpp isn't guarded the same way, so it still tries to instantiate json const j(filteredNums) on MinGW.
Happy to follow whatever you suggest.

@nlohmann

Copy link
Copy Markdown
Owner

Thanks for rebasing! I asked Claude Code to dig into this locally with g++-12 + libstdc++, which reproduces the failure, and I want to save you some cycles because a couple of the natural fixes don't actually work.

The red across the whole gcc/clang matrix isn't your new test — it's tests/src/unit-iterators2.cpp, which pipes the library's own j.items() (an iteration_proxy) and json through std::views::transform/reverse. Your new is_compatible_array_type_impl specialization then gets evaluated for views whose value type is iteration_proxy, and is_constructible<BasicJsonType, range_value_t<view>> re-enters the compatibility machinery → satisfaction of atomic constraint … depends on itself. It's a hard error, so it can't be SFINAE'd away after the fact, and it's why _GLIBCXX_RELEASE >= 12 doesn't help (gcc 12–15 all hit it) — please drop that guard.

Two things I checked with Claude:

  • Just excluding basic_json value types isn't enough — the poison value type here is iteration_proxy (a detail type), not basic_json. I tried !is_basic_json<range_value_t> and it still recurses.
  • You can't sidestep the trait either: on baseline, is_compatible_array_type<filter_view<…>> is already false, so the trait change is genuinely needed.

The crux is that any specialization keyed on std::ranges::view / range_value_t will be evaluated for the library's internal iteration_proxy during is_constructible<json, …> and that evaluation itself recurses. So the fix has to (a) avoid the self-referential is_constructible<json, range_value_t> in the constraint, and (b) exclude json's own detail range types up front with a plain (non-ranges) predicate, before any ranges concept is touched. And the test guard must mirror the feature guard exactly (the MinGW exclusion too), or MinGW stays red.

This one's genuinely fiddly template-wise. The key is to build unit-iterators2.cpp with g++-12/libstdc++ locally, since the C++20 standards jobs alone don't surface it.

Signed-off-by: Federico Sfriso <federicosfriso05@gmail.com>
@federicosfriso05-dotcom federicosfriso05-dotcom force-pushed the fix/issue-4916-ranges-view-support branch from 2ebbab6 to e11531b Compare July 1, 2026 09:58
@federicosfriso05-dotcom

Copy link
Copy Markdown
Author

Here's what changed after the last commit:

type_traits.hpp:

  • Added is_iteration_proxy_type<T> to identify nlohmann's internal proxy types before touching any std::ranges concept
  • Added is_compatible_range_view<T> with a two-level SafeToCheck guard to prevent circular constraints on GCC 12: excludes iteration_proxy/iteration_proxy_value, views wrapping them (e.g. owning_view<iteration_proxy<...>>), and views wrapping basic_json (e.g. ref_view<json>)
  • Made the iterator-based is_compatible_array_type_impl specialization explicitly exclude views to avoid ambiguity with types like std::string_view that satisfy both iterator traits and std::ranges::view

to_json.hpp:

  • Added a dedicated construct() overload for range views that iterates and pushes each element into the json array

I replaced _GLIBCXX_RELEASE >= 12 with JSON_HAS_RANGES && !defined(__MINGW32__) guard due to incomplete C++20 ranges support on MinGW.

After building unit-iterators2.cpp with GCC11, GCC12, Clang 18, MinGW and Apple Clang locally (and then the whole project), all CI checks are green.

Is there something I have to change? Happy to follow any your advice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants