如何通过cppyy将Python dict传入接收nlohmann::json的C++函数?
问题:cppyy中Python Dict转nlohmann::json的实现
问题复现
C++代码(test-json.h)
#include <iostream> #include <nlohmann/json.hpp> using nlohmann::json; void print_name_and_age(json j) { std::cout << j["name"] << "\n" << j["age"] << "\n"; }
Python代码(test-cppyy.py)
import cppyy cppyy.include('test-json.h') from cppyy.gbl import print_name_and_age some_dict = { "name": "alfred", "age": 25 } print_name_and_age(some_dict)
报错信息
print_name_and_age(some_dict) NotImplementedError: void ::print_name_and_age(nlohmann::json j) => NotImplementedError: could not convert argument 1 (this method cannot (yet) be called)
核心需求:实现Python dict直接传入接收nlohmann::json的C++函数,无需预处理编码,支持异构值(字符串、整数、浮点数、布尔值等)。
解决方案:自定义类型转换器
cppyy支持通过自定义转换器实现Python类型到C++类型的自动转换,针对nlohmann::json可按以下步骤实现:
步骤1:添加C++辅助转换函数
修改test-json.h,添加递归转换Python对象到nlohmann::json的逻辑(利用cppyy的CPyObject接口访问Python对象):
#include <iostream> #include <nlohmann/json.hpp> #include <cppyy/CPyCppyy.h> using nlohmann::json; // 递归转换Python对象到nlohmann::json json pyobj_to_json(Cppyy::CPyObject pyobj) { if (PyDict_Check(pyobj)) { json j; PyObject* keys = PyDict_Keys(pyobj); Py_ssize_t n = PyList_Size(keys); for (Py_ssize_t i = 0; i < n; ++i) { PyObject* key = PyList_GetItem(keys, i); PyObject* value = PyDict_GetItem(pyobj, key); std::string key_str = Cppyy::CPyObject(key).As<std::string>(); j[key_str] = pyobj_to_json(Cppyy::CPyObject(value)); } Py_DECREF(keys); return j; } else if (PyList_Check(pyobj) || PyTuple_Check(pyobj)) { json j; Py_ssize_t n = PySequence_Size(pyobj); for (Py_ssize_t i = 0; i < n; ++i) { PyObject* item = PySequence_GetItem(pyobj, i); j.push_back(pyobj_to_json(Cppyy::CPyObject(item))); Py_DECREF(item); } return j; } else if (PyLong_Check(pyobj)) { return pyobj.As<long long>(); } else if (PyFloat_Check(pyobj)) { return pyobj.As<double>(); } else if (PyUnicode_Check(pyobj)) { return pyobj.As<std::string>(); } else if (PyBool_Check(pyobj)) { return pyobj.As<bool>(); } return json(); } void print_name_and_age(json j) { std::cout << j["name"] << "\n" << j["age"] << "\n"; }
步骤2:在Python端注册转换器
修改Python代码,注册Python对象到nlohmann::json的自动转换规则:
import cppyy cppyy.include('test-json.h') from cppyy.gbl import print_name_and_age, pyobj_to_json from cppyy import add_conversion # 注册转换器:Python对象自动转nlohmann::json add_conversion("PyObject*", "nlohmann::json", lambda pyobj: pyobj_to_json(pyobj)) some_dict = { "name": "alfred", "age": 25, "is_student": True, "scores": [90.5, 88], "details": {"major": "CS", "year": 3} } print_name_and_age(some_dict)
运行后即可正常输出,支持嵌套异构结构的转换。
替代方案:使用std::map<std::string, std::any>或std::variant
如果自定义转换器成本较高,可考虑用以下两种类型替代nlohmann::json,cppyy对其原生支持更好:
1. std::map<std::string, std::any>
cppyy可自动将Python dict转换为std::map<std::string, std::any>:
- 要求C++17及以上版本
- 访问值时需显式转换(如
std::any_cast<int>(map["age"])) - 优点:无需额外代码,实现简洁
- 缺点:类型安全性低,访问时需手动判断类型,性能略低于nlohmann::json
示例C++函数:
#include <iostream> #include <map> #include <any> #include <string> void print_name_and_age(const std::map<std::string, std::any>& config) { std::cout << std::any_cast<std::string>(config.at("name")) << "\n"; std::cout << std::any_cast<int>(config.at("age")) << "\n"; }
Python端直接传入dict即可,无需额外处理。
2. std::map<std::string, std::variant<...>>
若能提前确定配置值的类型范围,std::variant比std::any更高效,cppyy同样支持自动转换:
#include <iostream> #include <map> #include <variant> #include <string> using ValueType = std::variant<std::string, int, double, bool>; void print_name_and_age(const std::map<std::string, ValueType>& config) { std::visit([](const auto& val) { std::cout << val << "\n"; }, config.at("name")); std::visit([](const auto& val) { std::cout << val << "\n"; }, config.at("age")); }
Python端直接传入dict,cppyy会自动匹配variant中的类型。
方案对比
| 方案 | 代码简洁性 | 性能 | 异构支持 | 类型安全性 |
|---|---|---|---|---|
| 自定义nlohmann::json转换器 | 中等(需编写转换逻辑) | 高 | 完全支持 | 高(nlohmann::json自动处理类型) |
| std::map+std::any | 高(无需额外代码) | 中等 | 完全支持 | 低(需手动判断类型) |
| std::map+std::variant | 较高(需预定义类型集合) | 高 | 有限支持(需提前确定类型) | 高(编译期类型检查) |
总结
- 优先选择自定义nlohmann::json转换器,完全匹配原有C++代码逻辑,支持任意异构结构,性能最优。
- 若追求快速实现,std::map<std::string, std::any> 是最简洁的方案,但需注意类型安全问题。
- 若能确定配置的类型范围,std::map<std::string, std::variant<...>> 兼顾性能与类型安全性。
内容的提问来源于stack exchange,提问作者Siraj Qazi
相关产品推荐
相关产品推荐

