diff --git a/include/pybind11_json/pybind11_json.hpp b/include/pybind11_json/pybind11_json.hpp index 2b7d9b2..930ae6a 100644 --- a/include/pybind11_json/pybind11_json.hpp +++ b/include/pybind11_json/pybind11_json.hpp @@ -9,6 +9,7 @@ #ifndef PYBIND11_JSON_HPP #define PYBIND11_JSON_HPP +#include #include #include @@ -67,8 +68,12 @@ namespace pyjson } } - inline nl::json to_json(const py::handle& obj) + inline nl::json to_json(const py::handle& obj, std::set& refs) { + if (auto [_, unique] = refs.insert(obj.ptr()); !unique) { + throw std::runtime_error("Circular reference detected"); + } + if (obj.ptr() == nullptr || obj.is_none()) { return nullptr; @@ -121,7 +126,7 @@ namespace pyjson auto out = nl::json::array(); for (const py::handle value : obj) { - out.push_back(to_json(value)); + out.push_back(to_json(value, refs)); } return out; } @@ -130,12 +135,19 @@ namespace pyjson auto out = nl::json::object(); for (const py::handle key : obj) { - out[py::str(key).cast()] = to_json(obj[key]); + out[py::str(key).cast()] = to_json(obj[key], refs); } return out; } throw std::runtime_error("to_json not implemented for this type of object: " + py::repr(obj).cast()); } + + inline nl::json to_json(const py::handle& obj) + { + std::set refs; + return to_json(obj, refs); + } + } // nlohmann_json serializers diff --git a/test/test_pybind11_json.cpp b/test/test_pybind11_json.cpp index 2d9cc82..b9ad588 100644 --- a/test/test_pybind11_json.cpp +++ b/test/test_pybind11_json.cpp @@ -481,3 +481,17 @@ TEST(pybind11_caster_fromjson, dict) ASSERT_EQ(j["number"].cast(), 1234); ASSERT_EQ(j["hello"].cast(), "world"); } + +TEST(pybind11_caster_tojson, recursive_dict) +{ + py::scoped_interpreter guard; + py::module m = create_module("test"); + + m.def("to_json", &test_fromtojson); + + // Simulate calling this binding from Python with a dictionary as argument + py::dict obj("number"_a=1234, "hello"_a="world"); + obj["recur"] = obj; + + ASSERT_ANY_THROW(m.attr("to_json")(obj)); +}