使用nlohmann-json时出现随机异常行为的技术问询
我实现了一个可处理nullptr的模板函数:
// get-function that can handle nullptr. template <typename T> static const T get(const nlohmann::json& json, const T& retOnNull = T()) { return json.is_null() ? retOnNull : json.get<T>(); }
调用方式如下:
nlohmann::json j; // ... // Load json from file into j auto ret = get<std::string>(j["SomeKey"], "");
我预期会出现以下三种情况之一:
- 若"SomeKey"存在且为字符串,则返回对应值;
- 若"SomeKey"不存在,则会以
null为默认值创建该键,传入函数后返回空字符串; - 若"SomeKey"存在但非字符串,则抛出
json.exception.type_error.302异常。
但实际情况是,即使"SomeKey"不存在,有时会随机抛出json.exception.type_error.302异常。经排查,传入get函数的数据并非null,而是类型为105的未定义数据。请问:
- 为何会出现未定义数据?
- 是否因新json条目因引用指针超出作用域导致?
- 为何异常是随机而非持续出现?
- 这种访问新键的方式是否无效?
1. 未定义数据的成因
你用j["SomeKey"]访问不存在的键时,nlohmann/json的默认行为确实会插入一个默认构造的json对象,但这个对象不是null——它是一个空的json对象(类型标识为7),而非你预期的null类型(类型标识为1)。你看到的类型105是无效的垃圾值,本质是因为你错误假设不存在的键会被初始化为null,实际插入的是空对象,后续如果该对象的内存被其他操作意外篡改,就会出现随机的无效类型值。
2. 关于引用指针超出作用域的疑问
不是引用指针超出作用域导致的。j["SomeKey"]返回的是对j内部新增元素的引用,只要j本身在作用域内,这个引用就是有效的。问题核心在于新增元素的类型和你预期的完全不符。
3. 异常随机出现的原因
异常随机是因为空对象的内存状态可能被后续json操作意外改写。比如当你后续对j进行插入、删除等操作时,内存布局变化可能导致这个空对象的类型标识被篡改,变成无效的105;而有时候内存没被改动,空对象的类型还是7,此时调用get<std::string>会因为类型不匹配抛出type_error.302——这时候你误以为是null传入函数,但实际是object类型,json.is_null()返回false,会执行json.get<T>,自然触发类型错误。
4. 这种访问新键的方式是否无效?
这种访问方式本身是nlohmann/json允许的,但完全不符合你的预期逻辑。如果你想访问不存在的键时得到null(或直接返回默认值)而非新增空对象,应该改用find方法检查键是否存在:
// 直接在调用处处理的写法 auto it = j.find("SomeKey"); auto ret = (it != j.end() && !it->is_null()) ? it->get<std::string>() : "";
或者修改你的get函数,内置键存在性检查:
template <typename T> static const T get(const nlohmann::json& json, const std::string& key, const T& retOnNull = T()) { auto it = json.find(key); if (it == json.end() || it->is_null()) { return retOnNull; } return it->get<T>(); }
调用时直接传入j和键名:
auto ret = get<std::string>(j, "SomeKey", "");
内容的提问来源于stack exchange,提问作者DaedalusAlpha

