mirror of
https://github.com/nlohmann/json.git
synced 2026-07-09 20:15:12 +00:00
Document std::optional<T> direct construction limitation (#5247)
* Document std::optional<T> direct-init/copy-init limitation with null
Add regression test pinning current behavior (CHECK_THROWS_AS) in the null
section of unit-conversions.cpp with detailed comment explaining the C++
language-level cause (std::optional's own converting constructor wins
overload resolution over basic_json::operator T()).
Add a warning callout in conversions.md documenting that direct construction/
assignment of std::optional<T> from JSON null throws type_error 302, with a
clear workaround (use get<std::optional<T>>() or get_to() instead, which
correctly produce std::nullopt).
This is a limitation at the language level: there is no SFINAE path to
distinguish "called from inside std::optional's own constructor" from "direct
call", so fixing it would require breaking changes to operator ValueType().
A permanent fix belongs in the 4.0 type-strictness redesign (#3453).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Fix issue reference in std::optional test comment
Update the comment in the null section test to reference #5246 instead of
placeholder #XXXX, clarifying where the direct-init/copy-init limitation is tracked.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Use CHECK_THROWS_AS_WITH for std::optional test assertions
Update the regression tests to use CHECK_THROWS_AS_WITH instead of
CHECK_THROWS_AS to verify both the exception type and the error message.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix CI: use CHECK_THROWS_WITH_AS, the macro that actually exists
CHECK_THROWS_AS_WITH is not a doctest macro; the correct one used throughout
this test suite is CHECK_THROWS_WITH_AS(expr, message, exception_type&), with
the message before the type and the type as a reference. The previous commit
didn't catch this because it only compiled the file standalone with default
settings; this TEST_CASE only compiles under
`#if !JSON_USE_IMPLICIT_CONVERSIONS`, which is why ci_test_noimplicitconversions
was the job that failed. Verified by building and running the test in that
exact configuration (JSON_USE_IMPLICIT_CONVERSIONS=0): 14/14 assertions pass.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Run std::optional test under default implicit-conversions build too
TEST_CASE("std::optional") was guarded by #if !JSON_USE_IMPLICIT_CONVERSIONS,
so it only ever compiled in the non-default build with implicit conversions
disabled. This traces back to commit 1d7688aef (fixes #3859), which changed a
previously dead #ifndef JSON_USE_IMPLICIT_CONVERSIONS guard (the macro is
always defined by that point, so it never held) to #if !JSON_USE_IMPLICIT_CONVERSIONS
-- making the test compile for the first time, but only in the disabled-conversions
build. As a result, std::optional support had zero test coverage in the default
configuration almost every user builds with.
Verified the entire test case (all sections: null, string, bool, number, array,
object) compiles and passes identically with JSON_USE_IMPLICIT_CONVERSIONS both
on (default) and off -- nothing in it actually depends on the setting. Removing
the guard closes the coverage gap with no behavior change: 285 assertions pass
with implicit conversions on, 232 with them off (the difference comes from
other, unrelated conditionally-compiled tests in this file).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* 🎓 fix warning
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,24 @@ which forces the explicit `get` form and can catch unintended conversions at com
|
||||
floating-point value as an integer truncates it, and narrowing conversions may overflow. See
|
||||
[number conversion](types/number_handling.md#number-conversion) for details and how to guard against it.
|
||||
|
||||
!!! warning "std::optional direct construction from JSON null throws"
|
||||
|
||||
Constructing or assigning `std::optional<T>` directly from a JSON value does not correctly produce
|
||||
`std::nullopt` for a JSON `null`:
|
||||
|
||||
```cpp
|
||||
json j_null;
|
||||
std::optional<std::string> opt = j_null; // ❌ throws type_error 302
|
||||
```
|
||||
|
||||
This is due to C++ language rules: `std::optional<T>` has its own converting constructor that is chosen over
|
||||
`basic_json::operator T()` when both are viable. Use `get<std::optional<T>>()` or `get_to()` instead:
|
||||
|
||||
```cpp
|
||||
auto opt = j_null.get<std::optional<std::string>>(); // ✅ std::nullopt
|
||||
j_null.get_to(opt); // ✅ std::nullopt
|
||||
```
|
||||
|
||||
## Putting values in
|
||||
|
||||
The reverse direction works the same way: assigning or constructing a `json` from a C++ value converts it to JSON.
|
||||
|
||||
Reference in New Issue
Block a user