qianqianxiao 2019-07-01
Json是数据交换常用的一种数据格式。C++标准库并没有包含任何Json标准的实现,所以我们要借助于一些第三方库,jsoncpp就是一个常用的库。由于工作中经常用jsoncpp,发现这个库问题不少,该篇文章本意是想深度吐槽一下这个函数库,但在寻找证据的过程中发现部分问题是由版本造成的,所以对问题进行一些说明,希望能帮助遇到同样问题的朋友。
这也是我在工作中遇到的问题:在程序处理非法json串时,直接导致服务程序终止,即使使用try
块包围这段代码,仍无法成功捕获异常。深入阅读它的实现代码才发现,jsoncpp的异常处理是比较原始的,遇到异常直接assert
,所以程序直接就终止了。对于简单的小程序来说,这无可厚非,但对于后端服务来说,这个问题非常严重,因为造成了服务不可用。
经过再度挖掘,发现开发环境用的jsoncpp版本是0.5.0,所以异常处理机制原始;实际上在jsoncpp 0.8.3开始就对异常处理进行了改变,通过定义JSON_USE_EXCEPTION
宏改变了类型无效的行为:
If defined, indicates that Json use exception to report invalid type manipulation instead of C assert macro.
在最新版的1.8.4里,JSON_USE_EXCEPTION=1
已经是默认行为了,除了注释错误仍然调用assert
外,基本上其他所有的错误都会抛出异常,不再assert
了:
If non-zero, the library uses exceptions to report bad input instead of C assertion macros. The default is to use exceptions.
所以,最简单的方案就是将库升级为最新版。如果因为一些原因无法升级函数库,那最稳妥的方式就是在使用数据之前进行严格的数据检查,除此之外没有其他更好的方式。
对于哪些错误可能导致异常,在jsoncpp.cpp
文件里直接搜关键字JSON_ASSERT
、JSON_FAIL_MESSAGE
和assert
,找到对应异常所在的代码,可以让你知道哪些具体情况会导致异常,以及哪些是可捕捉的,哪些会引起程序终止的。
说“蹩脚”,主要还是因为它提供的API太老套,明显没跟上现代C++的步伐,同时跟C++的各类容器交互困难,大部分情况需要人工写代码。比如:
现代C++增强了直观的初始化,比如map的初始化
std::map<int,int> map {{1,1},{2,2}};
但jsoncpp并没有提供这类json常量的初始化,我们只能通过:
Json::Value root; Json::FastWriter writer; root["name"] = "ideami"; std::string json = writer.write(root); std::cout << json << std::endl;
人工构造,或者使用:
Json::Reader reader; Json::Value root; std::string jsonStr = "{\"name\":\"ideami\"}"; if (!reader.parse(jsonStr, root)) { return -1; }
缺乏直观性。
如std::vector<int>
类型的数组,若生成json数组,我们只能:
#include <iostream> #include "json/json.h" #include <string> int main() { std::vector<int> v {1,2,3,4}; Json::Value array; Json::FastWriter writer; for (auto e : v) { array.append(e); } std::cout << writer.write(array) << std::endl; }
如果能提供直接转换的功能就方便了...
整体上,jsoncpp提供的API比较难用,上面用的API都是老版的API,其提供的新版APIJson::CharReaderBuilder
、Json::StreamWriterBuilder
等易用性也很差。
在易用性上,JSON for Modern C++是一个更好的选择。
请继续关注我的公众号文章