diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 491bb9873..fb48e4f82 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -287,7 +287,7 @@ struct external_constructor #ifdef JSON_HAS_CPP_17 template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) noexcept +void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_nothrow_assignable::value) { if (opt.has_value()) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6c98e7a8f..a606e1762 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6820,7 +6820,7 @@ struct external_constructor #ifdef JSON_HAS_CPP_17 template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) noexcept +void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_nothrow_assignable::value) { if (opt.has_value()) { diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index 077ba0e14..0d53f2226 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -1834,6 +1834,21 @@ TEST_CASE("std::u8string") #endif #endif +#if !defined(JSON_NOEXCEPTION) +namespace +{ +// a type whose to_json reports an error by throwing, used below to check that +// converting a std::optional to JSON propagates an exception thrown while +// converting its contained value instead of calling std::terminate (#5642) +struct throwing_to_json_type {}; + +[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/) +{ + throw std::runtime_error("cannot serialize throwing_to_json_type"); +} +} // namespace +#endif + TEST_CASE("std::optional") { SECTION("null") @@ -1916,6 +1931,23 @@ TEST_CASE("std::optional") CHECK(json(opt_object) == j_object); CHECK(std::map>(j_object) == opt_object); } + +#if !defined(JSON_NOEXCEPTION) + SECTION("exception from contained value's to_json propagates (#5642)") + { + // to_json(BasicJsonType&, const std::optional&) must not be + // noexcept: it calls T's to_json, which may throw (a user-defined + // to_json that reports an error, or std::bad_alloc for T = + // std::string/vector/json). Before the fix, this called + // std::terminate() instead of letting the exception propagate. + const std::optional opt = throwing_to_json_type{}; + CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); + + // the conversion is noexcept exactly when converting the contained value is + static_assert(!std::is_nothrow_constructible&>::value, ""); + static_assert(std::is_nothrow_constructible&>::value, ""); + } +#endif } #endif