cJSON项目解析

首先简单了解一下JSON。它是一种轻量级的数据交换格式。以一定的格式存储数据。看一个简单的例子就明白了:

{
	"name": "gao",
	"age": 21,
	"friends": ["张三", "李四"],
	"more": {
		"city": "湖北",
		"country": "中国"
	}
}

[]表示数组,{}表示对象,对象中包含键值对,值可以是对象、数组、数字、字符串或者三个字面值(false、null、true)中的一个,也说明了可以数组和对象可以嵌套使用。关于JSON详细介绍可以查阅相关资料,下面着重介绍cJSON项目。
cJSON是国外大神用c语言写的非常简单的生成和解析JSON数据格式的工具。只包含cJSON.h和cJSON.c两个文件,用一个main函数去调用相应的方法即可。
介绍一个JSON数据(生成解析均以下例为例)

{
	"奔驰":	{
		"factory":	"一汽大众",
		"last":	31,
		"price":83,
		"sell":	49,
		"sum":	80,
		"other":[123, 1, "hello world", 
					{
						"梅赛德斯":"心所向"
					}
				],
		"color":{
			"颜色":["红","绿","黑"]
		}
	}
}

下面用代码的方式生成这样一个格式的数据,并打印在控制台上:
数据包含四组{}对象、两组数组。最外层是一个大的对象obj,创建对象:

cJSON* obj = cJSON_CreateObject();

对象中包含奔驰键值对,值对应的又是一个对象subObj,subObj对象中包含一些键值对,对象中添加键值对:

cJSON_AddItemToObject(subObj, "factory", cJSON_CreateString("一汽大众"));
...

other键对应的值是一个数组array1,数组中包含一些元素和对象subsub1,数组中添加元素和对象:

//创建数组
cJSON* array1 =  cJSON_CreateArray();
cJSON_AddItemToArray(array1, cJSON_CreateNumber(123));
...
cJSON_AddItemToArray(array1, subsub1);

color键对应的值是一个对象subsub2,对象中颜色键对应的值是一个数组array2,因此需要创建array2数组,将该数组添加到subsub2对象中,color键值对再添加到奔驰对象中,最后将奔驰键值对添加到最大的obj对象中。
代码如下:

int main()
{
	//创建对象
	cJSON* obj = cJSON_CreateObject();

	//创建子对象(奔驰)
	cJSON* subObj = cJSON_CreateObject();

	//向奔驰对象中添加key-value
	cJSON_AddItemToObject(subObj, "factory", cJSON_CreateString("一汽大众"));
	cJSON_AddItemToObject(subObj, "last", cJSON_CreateNumber(31));
	cJSON_AddItemToObject(subObj, "price", cJSON_CreateNumber(83));
	cJSON_AddItemToObject(subObj, "sell", cJSON_CreateNumber(49));
	cJSON_AddItemToObject(subObj, "sum", cJSON_CreateNumber(80));

	//创建json数组(other)
	cJSON* array1 = cJSON_CreateArray();
	//向other数组中添加元素
	cJSON_AddItemToArray(array1, cJSON_CreateNumber(123));
	cJSON_AddItemToArray(array1, cJSON_CreateNumber(1));
	cJSON_AddItemToArray(array1, cJSON_CreateString("hello world"));

	//创建other中的对象
	cJSON* subsub1 = cJSON_CreateObject();
	cJSON_AddItemToObject(subsub1, "梅赛德斯", cJSON_CreateString("心所向"));

	//向other数组中添加对象
	cJSON_AddItemToArray(array1, subsub1);

	//向奔驰对象中添加other
	cJSON_AddItemToObject(subObj, "other", array1);

	//创建color中的subsub2对象
	cJSON* subsub2 = cJSON_CreateObject();
	//创建color中颜色数组
	cJSON* array2 = cJSON_CreateArray();

	//向颜色数组array2添加元素
	cJSON_AddItemToArray(array2, cJSON_CreateString("红"));
	cJSON_AddItemToArray(array2, cJSON_CreateString("绿"));
	cJSON_AddItemToArray(array2, cJSON_CreateString("黑"));

	//添加颜色数组到subsub2对象中
	cJSON_AddItemToObject(subsub2, "颜色", array2);

	//向奔驰对象中添加color key-value
	cJSON_AddItemToObject(subObj, "color", subsub2);

	//向obj中添加奔驰key-value
	cJSON_AddItemToObject(obj, "奔驰", subObj);

	//打印数据
	printf("带格式输出:\n");
	char* data = cJSON_Print(obj);
	printf("%s\n", data);
	printf("不带格式输出:\n");
	char* data1 = cJSON_PrintUnformatted(obj);
	printf("%s\n", data1);

	return 0;
}

在这里插入图片描述
下面将字符串解析成带格式的输出,需要注意把字符串中的双引号转义

int main()
{
	char* value = "{\"奔驰\":{\"factory\":\"一汽大众\",\"last\":31,\"price\":83, \
		\"sell\":49,\"sum\":80,\"other\":[123,1,\"hello world\",{\"梅赛德斯\":   \
		\"心所向\"}],\"color\":{\"颜色\":[\"红\",\"绿\",\"黑\"]}}}";	

	cJSON* obj1 = cJSON_Parse(value);

	char* data1 = cJSON_Print(obj1);

	printf("%s", data1);
}

在这里插入图片描述
下面具体说一下程序是如何运行的。
程序是采用双向链表的数据结构

/* The cJSON structure: */
typedef struct cJSON {
	struct cJSON *next,*prev;	/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
	struct cJSON *child;		/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */

	int type;					/* The type of the item, as above. */

	char *valuestring;			/* The item's string, if type==cJSON_String */
	int valueint;				/* The item's number, if type==cJSON_Number */
	double valuedouble;			/* The item's number, if type==cJSON_Number */

	char *string;				/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;

以下例为例看看数据在链表中究竟是如何存储的:

{
	"奔驰":	{
		"factory":	"一汽大众",
		"last":	31,
		"price":83,
		"color":{
			"颜色":["红","绿","黑"]
		}
	}
}

链表以逗号分隔结点:
在这里插入图片描述
提供了查询数组大小、删除结点等一系列方法:

int    cJSON_GetArraySize(cJSON *array);
void   cJSON_DeleteItemFromArray(cJSON *array,int which);
void   cJSON_DeleteItemFromObject(cJSON *object,const char *string);
...

程序最重要的是对字符串的解析,下面看一下程序是如何解析变换的。
跟踪源代码可以发现:
首先调用cJSON *cJSON_Parse(const char *value)传入压缩的字符串,再调用cJSON *cJSON_ParseWithOpts(const char *value,...)在该函数内调用static const char *parse_value(cJSON *item,const char *value)

/* Parser core - when encountering text, process appropriately. */
static const char *parse_value(cJSON *item,const char *value)
{
	if (!value)						return 0;	/* Fail on null. */
	if (!strncmp(value,"null",4))	{ item->type=cJSON_NULL;  return value+4; }
	if (!strncmp(value,"false",5))	{ item->type=cJSON_False; return value+5; }
	if (!strncmp(value,"true",4))	{ item->type=cJSON_True; item->valueint=1;	return value+4; }
	if (*value=='\"')				{ return parse_string(item,value); }
	if (*value=='-' || (*value>='0' && *value<='9'))	{ return parse_number(item,value); }
	if (*value=='[')				{ return parse_array(item,value); }
	if (*value=='{')				{ return parse_object(item,value); }

	ep=value;return 0;	/* failure. */
}

判断传入的value值第一个符号是什么,再分别调用相应的解析函数,因此要去除value中开头的空格,防止识别错误,这里用到一个函数,去除字符串开头的空格。

//去除字符串开头的空格
static const char *skip(const char *in) 
{	
	while (in && *in && (unsigned char)*in<=32) 
		in++; 
	return in;
}

这里认为ASCII为32之前的都是非法字符(空格的ASCII十进制为32),不得不赞叹代码的简洁啊。同时这也是学习源码的好处。
再回到parse_value函数中,这里JSON字符串是对象"{“开始,因此调用parse_object,在parse_object中通过parse_string函数解析键、parse_value解析值,遇到”,"说明不止一个键值对继续进行。对于进入parse_value如果值还为对象则继续调用parse_object如此递归循环直到结束。解析数组也是同样的道理。根据字符串建立双向链表,完成后返回链表的头结点。

static const char *parse_string(cJSON *item,const char *str);
static const char *parse_number(cJSON *item,const char *num);
static const char *parse_array(cJSON *item,const char *value);
static const char *parse_object(cJSON *item,const char *value);

打印有两种方式,按格式打印和没有格式打印

/* Render a cJSON item/entity/structure to text. */
char *cJSON_Print(cJSON *item)				{return print_value(item,0,1,0);}
char *cJSON_PrintUnformatted(cJSON *item)	{return print_value(item,0,0,0);}

区别就在于print_value中的第三个参数。

static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
{
        switch ((item->type)&255)
		{
			case cJSON_NULL:	out=cJSON_strdup("null");	break;
			case cJSON_False:	out=cJSON_strdup("false");break;
			case cJSON_True:	out=cJSON_strdup("true"); break;
			case cJSON_Number:	out=print_number(item,0);break;
			case cJSON_String:	out=print_string(item,0);break;
			case cJSON_Array:	out=print_array(item,depth,fmt,0);break;
			case cJSON_Object:	out=print_object(item,depth,fmt,0);break;
		}
}

事实上也就是根据该参数判断是否打印换行符和制表符,如果按格式打印,则输出相应的换行符和制表符,看起来更加直观。如果没有格式,则只是字符串。(效果见图一)

static char *print_number(cJSON *item,printbuffer *p);
static char *print_string(cJSON *item,printbuffer *p);
static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p);
static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p);

剩下的工作就是在相应的函数,处理字符串了。最终返回一个char*类型的字符串,打印输出即可。

Linux命令:
gdb调试中,调试带参数的程序
gdb --args ./filname arg1 arg2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高二的笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值