首先簡單瞭解一下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