简单封装cJSON方便使用

1 篇文章 0 订阅
1 篇文章 0 订阅

一、简介

一直使用cJSON来操作Json,感觉接口使用没那么方便。因为之前用过libconfig这个库,这个库使用很方便,于是萌生了封装一下的念头,封装成使用习惯和libconfig类似的。

封装过程不说了,增加了3个类Tree、Node、CException,都放到命名空间JsonTree中。

二、封装后使用方法

封装后可以很方便的操作增删改查了!

直接上使用的代码:

/* JsonTree解析Json,修改后更新Json
* 使用下面的Json字符串测试
{
	"node1": {
		"key1": null,
		"key2": "string1",
		"key3": [12, 34, 56, 78],
		"key4": [123, 456, 789],
		"sub1": {
			"key1": 94,
			"key2": "string2",
			"key3": [99, 88, 77, 66, 55, 44],
			"key4": [{
				"aaa": 111,
				"aaa2": "cow",
				"aaa3": ["a", "b", "c"]
			}, {
				"bbb": 222
			}, {
				"ccc": 333
			}, {
				"ddd": 444
			}],
			"sub1": {
				"key5": "Hello"
			}
		}
	}
}
*/

using namespace JsonTree;

static void JsonTreeDemo()
{
	std::string str = "{\n"
		"	\"node1\": {\n"
		"		\"key1\": null,\n"
		"		\"key2\": \"string1\",\n"
		"		\"key3\": [12, 34, 56, 78],\n"
		"		\"key4\": [123, 456, 789],\n"
		"		\"sub1\": {\n"
		"			\"key1\": 94,\n"
		"			\"key2\": \"string2\",\n"
		"			\"key3\": [99, 88, 77, 66, 55, 44],\n"
		"			\"key4\": [{\n"
		"				\"aaa\": 111,\n"
		"				\"aaa2\": \"cow\",\n"
		"				\"aaa3\": [\"a\", \"b\", \"c\"]\n"
		"			}, {\n"
		"				\"bbb\": 222\n"
		"			}, {\n"
		"				\"ccc\": 333\n"
		"			}, {\n"
		"				\"ddd\": 444\n"
		"			}],\n"
		"			\"sub1\": {\n"
		"				\"key5\": \"Hello\"\n"
		"			}\n"
		"		}\n"
		"	}\n"
		"}";

	try {
		// 通过Json转成Tree,可以方便的读取每项的值
		Tree tree(str);
		if (tree.IsValid())
		{
			Node& root = tree.Root();

			// 读取node1的keys,值是string1
			std::string strVal1 = root["node1"]["key2"];    // string1

			// 读取node1的sub1里面的key2
			std::string strVal2 = root["node1"]["sub1"]["key2"];  // string2

			// 数组访问
			// 比如读取node1->sub1->key4->aaa3, key4是数组, aaa3也是数组, aaa3是在数组key4的[0]中的,注意写法
			// 这个写法错误,会引发异常“NodeNoExistException: 'root.node1.sub1.key4.aaa3'  does not exist!”
			// std::string strVal3 = root["node1"]["sub1"]["key4"]["aaa3"][0];   
			// 正确写法
			std::string strVal3 = root["node1"]["sub1"]["key4"][0]["aaa3"][0];   // "a"
			std::string strVal4 = root["node1"]["sub1"]["key4"][0]["aaa3"][1];   // "b"
			std::string strVal5 = root["node1"]["sub1"]["key4"][0]["aaa3"][2];   // "c"
			// 支持数组长度
			size_t size = root["node1"]["sub1"]["key4"][0]["aaa3"].Lengeh();		// 3

			// 读取整个数组,比如:node1->key3
			size = root["node1"]["key3"].Lengeh();      // 先读取长度
			// 读取数组所有值
			for (size_t i = 0; i < size; ++i)
			{
				int nVal = root["node1"]["key3"][i];    // 分别是12,34,56,78
				std::cout << nVal << std::endl;
			}

			// Tree可以转换回Json对像,再使用cJSON_Print转回字符串
			// cJSON_Parse解析str, 再使用cJSON_Print转回字符串
			// 比较这两个字符串是相等的,确认Tree转回的Json也是正确的
			cJSON* refJson = tree.ToCJson();
			cJSON* json_root = cJSON_Parse(str.c_str());
			if (strcmp(cJSON_Print(refJson), cJSON_Print(json_root)) == 0)
			{
				std::cout << "Test Tree OK" << std::endl;
			}
			else
			{
				std::cout << "Test Tree error" << std::endl;
			}
			cJSON_Delete(json_root);

			// Tree可以删除某个项,比如删除node1->sub1->key3
			// 两种写法
			// 写法1
			Node& node1 = root["node1"]["sub1"];
			node1.remove_son("key3");
			// 写法2,比如删除node1->sub1->key2
			root["node1"]["sub1"]["key2"].remove();

			// 删除数组的某项,比如node1->key4是数组[123, 456, 789], 删除数组下标是[1]的456
			root["node1"]["key4"].remove_son(1);

			// 修改值,比如node1->sub1->key1值94,改成字符串值"I am a test"
			root["node1"]["sub1"]["key1"] = "I am a test";

			// 修改数组值,比如node1->key4是数组,上面删除456之后,是[123, 789],把123改成987
			root["node1"]["key4"][0] = 987;

			// 可以增加项,比如增加node1->here, 值是okok
			root["node1"].add_son("here");
			root["node1"]["here"] = "okok";

			// 打印出来看看结果是不是和希望的一样,打印还是转回Json,使用cJSON_Print转字符串打印
			refJson = tree.ToCJson();
			std::cout << "**************** new Json string *****************" << std::endl;
			std::cout << cJSON_Print(refJson) << std::endl;
			//printf("%s\n", cJSON_Print(refJson));

			// 析构函数会自动释放资源,这里的Tree是局部变量,函数返回后会自动析构
		}
	}
	catch (std::exception& e) {			// 可以捕捉异常
		std::cerr << e.what() << std::endl;
	}
}

最后结果:

12
34
56
78
Test Tree OK
**************** new Json string *****************
{
        "node1":        {
                "key1": null,
                "key2": "string1",
                "key3": [12, 34, 56, 78],
                "key4": [987, 789],
                "sub1": {
                        "key1": "I am a test",
                        "key4": [{
                                        "aaa":  111,
                                        "aaa2": "cow",
                                        "aaa3": ["a", "b", "c"]
                                }, {
                                        "bbb":  222
                                }, {
                                        "ccc":  333
                                }, {
                                        "ddd":  444
                                }],
                        "sub1": {
                                "key5": "Hello"
                        }
                },
                "here": "okok"
        }
}

从例子中发现,接口使用比原来使用简单容易了,使用上接近libconfig

这个简单封装用法,在实际项目中使用了一段比较长的时间了(本文是vs2019使用写的,但是在海思的ARM平台使用较长时间了,也能用于Linux平台的),常见的Json暂未发现问题。

三、速度对比

后来,因为要在服务器中使用Json,因为服务器高并发,需要考虑速度因素,几经查找,看到号称速度很快的RapidJson,然后了解了一下接口的用法,还是觉得接口比较难使用,和cJSON差不多,而且有人说某些情况下会有内存泄漏,所以放弃了。

之后查到有[JSON for Modern C++,nlohmann写的json,这个接口非常的好用,是用过的最好用的,用法上和自己封装之后的用法类似,但是是大牛写的,肯定更符合标准化,使用上更放心,于是打算测试一下cJSON、JSON for Modern C++、自己封装的JsonTree三者的速度,可以肯定JsoncJSON上封装的,速度肯定是慢于cJSON,如果JSON for Modern C++速度优于或者接近cJSON,那就可以放心使用了。

测试方式是跑10万个Json字符串解析,读取出里面每个key的值,测试执行时间,因为仿造10万个Json比较困难,只是测试速度,所以用一个统一的Json字符串,按照每次Json都不同的流程那样的执行来测试。

测试代码如下:

/*
* 以下Json,解释10万次,分别测试测试cJSON、JsonTree、Json for Modern C++所用时间
{
	"name": "server",
	"id": 123456,
	"sql_ip": "192.168.1.100",
	"database": "shops",
	"user": "root",
	"pwd": "user@test.com",
	"param1": 111,
	"param2": 222,
	"param3": "333",
	"array": [{
		"ip": "192.168.1.200",
		"port": 7000
	}, {
		"ip": "192.168.1.200",
		"port": 7000
	}, {
		"ip": "192.168.1.200",
		"port": 7000
	}]
}
*/

typedef struct
{
	std::string ip;
	int port;
}Network;

// 测试cJSON的时间
static void TestcJSON()
{
	clock_t start_time = clock();
	for (int i = 0; i < 100000; ++i)   // 10万次
	{
		std::string str = "{\n"
			"	\"name\": \"server\",\n"
			"	\"id\": 123456,\n"
			"	\"sql_ip\": \"192.168.1.100\",\n"
			"	\"database\": \"shops\",\n"
			"	\"user\": \"root\",\n"
			"	\"pwd\": \"user@test.com\",\n"
			"	\"param1\": 111,\n"
			"	\"param2\": 222,\n"
			"	\"param3\": \"333\",\n"
			"	\"array\": [{\n"
			"		\"ip\": \"192.168.1.200\",\n"
			"		\"port\": 7000\n"
			"	}, {\n"
			"		\"ip\": \"192.168.1.200\",\n"
			"		\"port\": 7001\n"
			"	}, {\n"
			"		\"ip\": \"192.168.1.200\",\n"
			"		\"port\": 7002\n"
			"	}]\n"
			"}";

		std::string name;
		int id;
		std::string sql_ip;
		std::string database;
		std::string user;
		std::string pwd;
		int param1 = 0;
		int param2 = 0;
		std::string param3;
		std::vector<Network> vecNetwork;
		cJSON * json_root = cJSON_Parse(str.c_str());
		if (json_root != NULL)
		{
			name = cJSON_GetObjectItem(json_root, "name")->valuestring;
			id = cJSON_GetObjectItem(json_root, "id")->valueint;
			sql_ip = cJSON_GetObjectItem(json_root, "sql_ip")->valuestring;
			database = cJSON_GetObjectItem(json_root, "database")->valuestring;
			user = cJSON_GetObjectItem(json_root, "user")->valuestring;
			pwd = cJSON_GetObjectItem(json_root, "pwd")->valuestring;
			param1 = cJSON_GetObjectItem(json_root, "param1")->valueint;
			param2 = cJSON_GetObjectItem(json_root, "param2")->valueint;
			param3 = cJSON_GetObjectItem(json_root, "param3")->valuestring;

			cJSON* json_arry = cJSON_GetObjectItem(json_root, "array");
			if (json_arry != NULL)
			{
				cJSON* json_child = json_arry->child;
				while (json_child != NULL) {
					std::string strip = cJSON_GetObjectItem(json_child, "ip")->valuestring;
					int port = cJSON_GetObjectItem(json_child, "port")->valueint;
					vecNetwork.push_back({ strip, port });
					json_child = json_child->next;
				}
			}
		}
		cJSON_Delete(json_root);
	}
	clock_t end_time = clock();
	std::cout << "cJSON Running time is: " << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << std::endl;
}

// 测试JsonTree的时间
using namespace JsonTree;
static void TestJsonTree()
{
	clock_t start_time = clock();
	for (int i = 0; i < 100000; ++i)
	{
		std::string str = "{\n"
			"	\"name\": \"server\",\n"
			"	\"id\": 123456,\n"
			"	\"sql_ip\": \"192.168.1.100\",\n"
			"	\"database\": \"shops\",\n"
			"	\"user\": \"root\",\n"
			"	\"pwd\": \"user@test.com\",\n"
			"	\"param1\": 111,\n"
			"	\"param2\": 222,\n"
			"	\"param3\": \"333\",\n"
			"	\"array\": [{\n"
			"		\"ip\": \"192.168.1.200\",\n"
			"		\"port\": 7000\n"
			"	}, {\n"
			"		\"ip\": \"192.168.1.200\",\n"
			"		\"port\": 7001\n"
			"	}, {\n"
			"		\"ip\": \"192.168.1.200\",\n"
			"		\"port\": 7002\n"
			"	}]\n"
			"}";

		JsonTree::Tree tree(str);
		if (tree.IsValid())
		{
			JsonTree::Node& root = tree.Root();
			std::string name = root["name"];
			int id = root["id"];
			std::string sql_ip = root["sql_ip"];
			std::string database = root["database"];
			std::string user = root["user"];
			std::string pwd = root["pwd"];
			int param1 = root["param1"];
			int param2 = root["param2"];
			std::string param3 = root["param3"];
			size_t len = root["array"].Lengeh();
			std::vector<Network> vecNetwork;
			for (size_t j = 0; j < len; ++j)
			{
				std::string strip = root["array"][j]["ip"];
				int port = root["array"][j]["port"];
				vecNetwork.push_back({ strip, port });
			}
		}
	}
	clock_t end_time = clock();
	std::cout << "JsonTree Running time is: " << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << std::endl;
}

// 测试nlohmann Json 的时间
#include "nlohmann/json.hpp"   // 要使用nlohmann json 必须把文件夹nlohmann放到工程,以及包含里面的头文件json.hpp

using json = nlohmann::json;

static void TestNlohmannJson()
{
	clock_t start_time = clock();
	for (int i = 0; i < 100000; ++i)
	{
		json root = R"(
			{
				"name": "server",
				"id": 123456,
				"sql_ip": "192.168.1.100",
				"database": "shops",
				"user": "root",
				"pwd": "user@test.com",
				"param1": 111,
				"param2": 222,
				"param3": "333",
				"array": [{
					"ip": "192.168.1.200",
					"port": 7000
				}, {
					"ip": "192.168.1.200",
					"port": 7001
				}, {
					"ip": "192.168.1.200",
					"port": 7002
				}]
			}		
		)"_json;

		std::string name = root["name"];
		int id = root["id"];
		std::string sql_ip = root["sql_ip"];
		std::string database = root["database"];
		std::string user = root["user"];
		std::string pwd = root["pwd"];
		int param1 = root["param1"];
		int param2 = root["param2"];
		std::string param3 = root["param3"];
		size_t len = root["array"].size();
		std::vector<Network> vecNetwork;
		for (size_t j = 0; j < len; ++j)
		{
			std::string strip = root["array"][j]["ip"];
			int port = root["array"][j]["port"];
			vecNetwork.push_back({ strip, port });
		}
	}
	clock_t end_time = clock();
	std::cout << "NlohmannJson Running time is: " << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << std::endl;
}


static void TestSpeed()
{
	TestcJSON();
	TestJsonTree();
	TestNlohmannJson();

	/* 为了表示测试和调用顺序无关,顺序反过来再测试1次 */
	TestNlohmannJson();
	TestJsonTree();
	TestcJSON();
}

测试两次,第2次把测试顺序反过来。每个测试都是10万次。

结果是:

cJSON Running time is: 4317ms
JsonTree Running time is: 29087ms
NlohmannJson Running time is: 54067ms
NlohmannJson Running time is: 54109ms
JsonTree Running time is: 29295ms
cJSON Running time is: 4008ms

可见JSON for Modern C++的接口使用最好,JsonTree用法和它比较相似。

然而,封装的代价是巨大的,cJSON只用4秒多点,封装之后29秒多点,时间多了约7倍,JSON for Modern C++最慢,54秒多点,是cJSON的12倍多,是JsonTree的1.8倍左右。

高并发服务器,最后我只能选择cJSON,毕竟速度还是最重要的。

如果哪位程序员朋友有又好用又快速的程序,请给我介绍。最后附上整个源程序项目供有兴趣的朋友下载。

四、源码下载

源码下载VS2019编译测试通过。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值