首先简单了解一下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