如何以易读方式处理nlohmann json的type_error异常
我用nlohmann json解析config.json文件,该文件会被其他开发者修改。现有代码如下:
try { conf.x1 = data.at("x1"); conf.x2 = data.at("x2"); // ... conf.xN = data.at("xN"); } catch (const nlohmann::json::out_of_range &e) { std::cerr << "Error. " << e.what() << std::endl; return std::nullopt; } catch (const nlohmann::json::type_error &e) { std::cerr << "Error. " << e.what() << std::endl; return std::nullopt; }
捕获nlohmann::json::out_of_range异常时,e.what()会明确提示缺失的键(比如[json.exception.out_of_range.403] key 'x1' not found),但捕获nlohmann::json::type_error异常时,只能得到类似[json.exception.type_error.302] type must be number, but is string的提示,无法定位具体是哪个配置项类型错误,需要用户逐一排查文件。我曾考虑编写如int read_data(const std::string key)的封装函数,通过模板或多类型函数解决返回值问题,但担心代码复杂化,希望找到类似out_of_range异常提示的简便替代方案。
方法1:单个字段解析加局部try-catch
放弃用大try块包裹所有解析逻辑,给每个字段的解析单独添加小范围的try-catch,直接在catch块中明确输出当前出错的键名:
bool parseConfig(const nlohmann::json& data, Config& conf) { // 解析x1 try { conf.x1 = data.at("x1"); } catch (const nlohmann::json::exception& e) { std::cerr << "Error with key 'x1': " << e.what() << std::endl; return false; } // 解析x2 try { conf.x2 = data.at("x2"); } catch (const nlohmann::json::exception& e) { std::cerr << "Error with key 'x2': " << e.what() << std::endl; return false; } // 其他字段同理... return true; }
这种方式最直观,代码改动极小,不需要额外封装,每个字段的错误都能精准定位,完全满足需求。
方法2:极简模板封装函数
写一个轻量的模板函数,兼顾复用性和简洁性,自动带上键名信息:
template<typename T> bool getConfigValue(const nlohmann::json& data, const std::string& key, T& out) { try { out = data.at(key); return true; } catch (const nlohmann::json::exception& e) { std::cerr << "Error accessing key '" << key << "': " << e.what() << std::endl; return false; } }
调用时非常简洁,逻辑清晰:
if (!getConfigValue(data, "x1", conf.x1) || !getConfigValue(data, "x2", conf.x2) /* || 其他字段 */) { return std::nullopt; }
这个模板函数仅做取值和错误提示,代码量小,不会让原有逻辑变得复杂。
方法3:用get_to结合手动类型提示
nlohmann json提供的get_to方法不会抛出异常,而是返回bool表示是否成功。可以结合它手动添加字段和类型信息:
// 先定义一个简单的类型名映射函数 template<typename T> std::string getFriendlyTypeName() { if constexpr (std::is_same_v<T, int>) return "integer"; else if constexpr (std::is_same_v<T, std::string>) return "string"; else if constexpr (std::is_same_v<T, double>) return "floating-point number"; // 根据需要扩展其他类型 return "unknown type"; } // 解析示例 if (!data.at("x1").get_to(conf.x1)) { std::cerr << "Error parsing key 'x1': expected " << getFriendlyTypeName<decltype(conf.x1)>() << std::endl; return std::nullopt; }
这种方式不需要处理异常,也能精准定位错误字段,类型提示更友好。
内容的提问来源于stack exchange,提问作者Mo_

