Skip to content

std::optional<T> direct construction from JSON null throws instead of yielding std::nullopt #5246

Description

@nlohmann

Description

Constructing or assigning std::optional<T> directly from a JSON null value throws type_error instead of yielding std::nullopt. Only the explicit .get<std::optional<T>>() form works correctly.

Reproduction

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

int main() {
    nlohmann::json j_null;
    
    // Works correctly
    auto opt1 = j_null.get<std::optional<std::string>>();  // → std::nullopt ✓
    
    // Throws type_error 302
    std::optional<std::string> opt2 = j_null;  // ✗ throws
    std::optional<std::string> opt3(j_null);   // ✗ throws (direct-init also fails)
}

Error: [json.exception.type_error.302] type must be string, but is null

Expected behavior

Direct construction/assignment should yield std::nullopt, consistent with .get<std::optional<T>>().

Environment

  • Compiler: GCC 16.1.0, Apple Clang 21.0.0
  • C++ standard: C++17 and later
  • Library version: develop branch

Why this cannot be cleanly fixed

This is a limitation at the C++ language level, not a library bug:

  1. std::optional<T> has its own converting constructor that accepts any type constructible from T
  2. T (e.g., std::string) is constructible from basic_json via basic_json::operator T()
  3. When both constructors are viable, std::optional's own constructor wins overload resolution
  4. There is no SFINAE trick to distinguish "being called from inside std::optional's own constructor" from a direct call

A proper fix would require restricting or redesigning basic_json::operator ValueType() (json.hpp:1946-1971), which is relied upon for all ordinary implicit conversions (e.g., std::string s = json_obj;). Such a change would be a breaking change to the core conversion API and belongs in a major-version redesign.

Workaround

Use .get<std::optional<T>>() or .get_to() for std::optional<T> conversions:

auto opt = j_null.get<std::optional<std::string>>();  // → std::nullopt ✓
j_null.get_to(opt);                                     // → std::nullopt ✓

Related


Note: This issue was filed by Claude Code to track a limitation separate from #4864.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions