Skip to content

to_json(std::optional<T>) is noexcept: an exception from the contained value calls std::terminate #5642

Description

@nlohmann

Description

Converting a std::optional<T> to JSON calls std::terminate() instead of propagating the exception when the conversion of the contained value throws. The overload is declared noexcept, but its body assigns *opt to the JSON value, which calls the user's to_json (or allocates):

include/nlohmann/detail/conversions/to_json.hpp#L290

void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept
{
    if (opt.has_value())
    {
        j = *opt;   // may throw: user to_json, std::bad_alloc for strings/arrays/objects
    }
    ...

Anything that can throw inside j = *opt is affected: a user-defined to_json that reports an error by throwing, std::bad_alloc for std::optional<std::string>, std::optional<std::vector<...>>, or std::optional<json>, and exceptions from nested conversions (for example a type defined with NLOHMANN_DEFINE_TYPE_* that has a std::optional member whose value's to_json throws). The noexcept also propagates into the noexcept specification of basic_json's converting constructor, so the whole json j = opt; expression is noexcept(true).

Since when: the noexcept was added in #4742 (commit 96c1b52, 2025-04-15), which was merged after the v3.12.0 tag. The program below prints caught: cannot serialize Value with the v3.12.0 headers and terminates with develop, so no release is affected yet.

Open PR #5591 changes the body of this function (j = BasicJsonType(*opt);) but keeps the noexcept.

Possible fix: remove the noexcept (or make it conditional on the conversion of T being noexcept). I tested removing it: the program below then prints caught: cannot serialize Value. I did not run the full test suite with that change; no test in tests/src checks the noexcept-ness of this overload.

Reproduction steps

  1. Save the program below as c01.cpp.
  2. Compile it against develop: clang++ -std=c++17 -I include c01.cpp -o c01 (the same happens with -fsanitize=address,undefined and with GCC 16.2).
  3. Run ./c01.

Expected vs. actual results

  • Expected: the std::runtime_error thrown by to_json(json&, const Value&) propagates, and the program prints caught: cannot serialize Value.
  • Actual: std::terminate() is called and the process aborts.

Minimal code example

#include <nlohmann/json.hpp>
#include <iostream>
#include <optional>
#include <stdexcept>

using json = nlohmann::json;

struct Value {};

// a to_json that reports an error by throwing
void to_json(json&, const Value&)
{
    throw std::runtime_error("cannot serialize Value");
}

int main()
{
    std::optional<Value> opt = Value{};
    try
    {
        json j = opt;  // expected: the runtime_error propagates to the catch below
    }                  // actual:   std::terminate() is called
    catch (const std::exception& e)
    {
        std::cout << "caught: " << e.what() << '\n';
    }
}

Error messages

libc++abi: terminating due to uncaught exception of type std::runtime_error: cannot serialize Value
(process aborted, exit code 134)

With GCC 16.2 / libstdc++:

terminate called after throwing an instance of 'std::runtime_error'
  what():  cannot serialize Value
(process aborted, exit code 134)

Compiler and operating system

Apple clang 21.0.0 (clang-2100.3.34.2), macOS 27.0 (arm64); also GCC 16.2 (/opt/homebrew/bin/g++-16)

Library version

develop @ 633de8e (not reproducible with 3.12.0, see above)

Validation

  • The bug also occurs if the latest version from the develop branch is used.
  • I can successfully compile and run the unit tests.

This issue was written by Claude Code on behalf of @nlohmann.

Activity

  1. self-assigned this
    on Sep 29, 2026
  2. added this to the Release 3.13.0 milestone on Sep 30, 2026
  3. added a commit that references this issue on Oct 1, 2026
    42f89e3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

kind: bugsolution: proposed fixa fix for the issue has been proposed and waits for confirmation

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions