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