diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 491bb9873..d77381249 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) { if (opt.has_value()) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 576498738..83b6f10be 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6816,7 +6816,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) { if (opt.has_value()) { diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index 90d972f71..6e0c73b48 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -1826,6 +1826,16 @@ TEST_CASE("std::u8string") #endif #endif +// 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 {}; + +void to_json(json&, const throwing_to_json_type&) +{ + throw std::runtime_error("cannot serialize throwing_to_json_type"); +} + TEST_CASE("std::optional") { SECTION("null") @@ -1908,6 +1918,19 @@ 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&); + } +#endif } #endif