出自https://blog.csdn.net/Mculover666/article/details/103796256
1. JSON與cJSON
JSON —— 輕量級的數據格式
JSON 全稱 JavaScript Object Notation,即 JS對象簡譜,是一種輕量級的數據格式。
它采用完全獨立於編程語言的文本格式來存儲和表示數據,語法簡潔、層次結構清晰,易於人閱讀和編寫,同時也易於機器解析和生成,有效的提升了網絡傳輸效率。
JSON語法規則
JSON對象是一個無序的"名稱/值"鍵值對的集合:
- 以"
{
“開始,以”}
"結束,允許嵌套使用; - 每個名稱和值成對出現,名稱和值之間使用"
:
"分隔; - 鍵值對之間用"
,
"分隔 - 在這些字符前后允許存在無意義的空白符;
對於鍵值,可以有如下值:
- 一個新的json對象
- 數組:使用"
[
“和”]
"表示 - 數字:直接表示,可以是整數,也可以是浮點數
- 字符串:使用引號
"
表示 - 字面值:false、null、true中的一個(必須是小寫)
示例如下:
{ "name": "mculover666", "age": 22, "weight": 55.5 "address": { "country": "China", "zip-code": 111111 }, "skill": ["c", "Java", "Python"], "student": false }
cJSON
cJSON是一個使用C語言編寫的JSON數據解析器,具有超輕便,可移植,單文件的特點,使用MIT開源協議。
cJSON項目托管在Github上,倉庫地址如下:
https://github.com/DaveGamble/cJSON
使用Git命令將其拉取到本地:
git clone https://github.com/DaveGamble/cJSON.git
- 1
從Github拉取cJSON源碼后,文件非常多,但是其中cJSON的源碼文件只有兩個:
cJSON.h
cJSON.c
使用的時候,只需要將這兩個文件復制到工程目錄,然后包含頭文件cJSON.h
即可,如下:
#include "cJSON.h"
2. cJSON數據結構和設計思想
cJSON的設計思想從其數據結構上就能反映出來。
cJSON使用cJSON結構體來表示一個JSON數據,定義在cJSON.h
中,源碼如下:
/* The cJSON structure: */ typedef struct cJSON { /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ struct cJSON *next; struct cJSON *prev; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ struct cJSON *child; /* The type of the item, as above. */ int type; /* The item's string, if type==cJSON_String and type == cJSON_Raw */ char *valuestring; /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ int valueint; /* The item's number, if type==cJSON_Number */ double valuedouble; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ char *string; } cJSON;
cJSON的設計很巧妙。
首先,它不是將一整段JSON數據抽象出來,而是將其中的一條JSON數據抽象出來,也就是一個鍵值對,用上面的結構體 strcut cJSON
來表示,其中用來存放值的成員列表如下:
String
:用於表示該鍵值對的名稱;type
:用於表示該鍵值對中值的類型;valuestring
:如果鍵值類型(type)是字符串,則將該指針指向鍵值;valueint
:如果鍵值類型(type)是整數,則將該指針指向鍵值;valuedouble
:如果鍵值類型(type)是浮點數,則將該指針指向鍵值;
其次,一段完整的JSON數據中由很多鍵值對組成,並且涉及到鍵值對的查找、刪除、添加,所以使用鏈表來存儲整段JSON數據,如上面的代碼所示:
next
指針:指向下一個鍵值對prev
指針指向上一個鍵值對
最后,因為JSON數據支持嵌套,所以一個鍵值對的值會是一個新的JSON數據對象(一條新的鏈表),也有可能是一個數組,方便起見,在cJSON中,數組也表示為一個數組對象,用鏈表存儲,所以:
在鍵值對結構體中,當該鍵值對的值是一個嵌套的JSON數據或者一個數組時,由child
指針指向該條新鏈表。
3. JSON數據封裝
封裝方法
封裝JSON數據的過程,其實就是創建鏈表和向鏈表中添加節點的過程。
首先來講述一下鏈表中的一些術語:
- 頭指針:指向鏈表頭結點的指針;
- 頭結點:不存放有效數據,方便鏈表操作;
- 首節點:第一個存放有效數據的節點;
- 尾節點:最后一個存放有效數據的節點;
明白了這幾個概念之后,我們開始講述創建一段完整的JSON數據,即如何創建一條完整的鏈表。
- ① 創建頭指針:
cJSON* cjson_test = NULL;
- 1
- ② 創建頭結點,並將頭指針指向頭結點:
cjson_test = cJSON_CreateObject();
- 1
- ③ 盡情的向鏈表中添加節點:
cJSON_AddNullToObject(cJSON * const object, const char * const name);
cJSON_AddTrueToObject(cJSON * const object, const char * const name);
cJSON_AddFalseToObject(cJSON * const object, const char * const name);
cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
cJSON_AddObjectToObject(cJSON * const object, const char * const name);
cJSON_AddArrayToObject(cJSON * const object, const char * const name);
輸出JSON數據
上面講述,一段完整的JSON數據就是一條長長的鏈表,那么,如何打印出這段JSON數據呢?
cJSON提供了一個API,可以將整條鏈表中存放的JSON信息輸出到一個字符串中:
(char *) cJSON_Print(const cJSON *item);
使用的時候,只需要接收該函數返回的指針地址即可。
封裝數據和打印數據示例
單純的講述方法還不夠,下面用一個例子來說明,封裝出開頭給出的那段JSON數據:
#include <stdio.h> #include "cJSON.h" int main(void) { cJSON* cjson_test = NULL; cJSON* cjson_address = NULL; cJSON* cjson_skill = NULL; char* str = NULL; /* 創建一個JSON數據對象(鏈表頭結點) */ cjson_test = cJSON_CreateObject(); /* 添加一條字符串類型的JSON數據(添加一個鏈表節點) */ cJSON_AddStringToObject(cjson_test, "name", "mculover666"); /* 添加一條整數類型的JSON數據(添加一個鏈表節點) */ cJSON_AddNumberToObject(cjson_test, "age", 22); /* 添加一條浮點類型的JSON數據(添加一個鏈表節點) */ cJSON_AddNumberToObject(cjson_test, "weight", 55.5); /* 添加一個嵌套的JSON數據(添加一個鏈表節點) */ cjson_address = cJSON_CreateObject(); cJSON_AddStringToObject(cjson_address, "country", "China"); cJSON_AddNumberToObject(cjson_address, "zip-code", 111111); cJSON_AddItemToObject(cjson_test, "address", cjson_address); /* 添加一個數組類型的JSON數據(添加一個鏈表節點) */ cjson_skill = cJSON_CreateArray(); cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" )); cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java" )); cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" )); cJSON_AddItemToObject(cjson_test, "skill", cjson_skill); /* 添加一個值為 False 的布爾類型的JSON數據(添加一個鏈表節點) */ cJSON_AddFalseToObject(cjson_test, "student"); /* 打印JSON對象(整條鏈表)的所有數據 */ str = cJSON_Print(cjson_test); printf("%s\n", str); return 0; }
編譯運行:
gcc cJSON.c example1.c -o example1.exe
實驗結果如圖:
該JSON數據鏈表的關系如圖:
4. cJSON數據解析
解析方法
解析JSON數據的過程,其實就是剝離一個一個鏈表節點(鍵值對)的過程。
解析方法如下:
- ① 創建鏈表頭指針:
cJSON* cjson_test = NULL;
- ② 解析整段JSON數據,並將鏈表頭結點地址返回,賦值給頭指針:
解析整段數據使用的API只有一個:
(cJSON *) cJSON_Parse(const char *value);
- ③ 根據鍵值對的名稱從鏈表中取出對應的值,返回該鍵值對(鏈表節點)的地址
(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
- ④ 如果JSON數據的值是數組,使用下面的兩個API提取數據:
(int) cJSON_GetArraySize(const cJSON *array); (cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
解析示例
下面用一個例子來說明如何解析出開頭給出的那段JSON數據:
#include <stdio.h> #include "cJSON.h" char *message = "{ \ \"name\":\"mculover666\", \ \"age\": 22, \ \"weight\": 55.5, \ \"address\": \ { \ \"country\": \"China\",\ \"zip-code\": 111111\ }, \ \"skill\": [\"c\", \"Java\", \"Python\"],\ \"student\": false \ }"; int main(void) { cJSON* cjson_test = NULL; cJSON* cjson_name = NULL; cJSON* cjson_age = NULL; cJSON* cjson_weight = NULL; cJSON* cjson_address = NULL; cJSON* cjson_address_country = NULL; cJSON* cjson_address_zipcode = NULL; cJSON* cjson_skill = NULL; cJSON* cjson_student = NULL; int skill_array_size = 0, i = 0; cJSON* cjson_skill_item = NULL; /* 解析整段JSO數據 */ cjson_test = cJSON_Parse(message); if(cjson_test == NULL) { printf("parse fail.\n"); return -1; } /* 依次根據名稱提取JSON數據(鍵值對) */ cjson_name = cJSON_GetObjectItem(cjson_test, "name"); cjson_age = cJSON_GetObjectItem(cjson_test, "age"); cjson_weight = cJSON_GetObjectItem(cjson_test, "weight"); printf("name: %s\n", cjson_name->valuestring); printf("age:%d\n", cjson_age->valueint); printf("weight:%.1f\n", cjson_weight->valuedouble); /* 解析嵌套json數據 */ cjson_address = cJSON_GetObjectItem(cjson_test, "address"); cjson_address_country = cJSON_GetObjectItem(cjson_address, "country"); cjson_address_zipcode = cJSON_GetObjectItem(cjson_address, "zip-code"); printf("address-country:%s\naddress-zipcode:%d\n", cjson_address_country->valuestring, cjson_address_zipcode->valueint); /* 解析數組 */ cjson_skill = cJSON_GetObjectItem(cjson_test, "skill"); skill_array_size = cJSON_GetArraySize(cjson_skill); printf("skill:["); for(i = 0; i < skill_array_size; i++) { cjson_skill_item = cJSON_GetArrayItem(cjson_skill, i); printf("%s,", cjson_skill_item->valuestring); } printf("\b]\n"); /* 解析布爾型數據 */ cjson_student = cJSON_GetObjectItem(cjson_test, "student"); if(cjson_student->valueint == 0) { printf("student: false\n"); } else { printf("student:error\n"); } return 0; }
編譯:
gcc cJSON.c example2.c -o example2.exe
運行結果如圖:
注意事項
在本示例中,因為我提前知道數據的類型,比如字符型或者浮點型,所以我直接使用指針指向對應的數據域提取,在實際使用時,如果提前不確定數據類型,應該先判斷type的值,確定數據類型,再從對應的數據域中提取數據。
5. cJSON使用過程中的內存問題
內存及時釋放
cJSON的所有操作都是基於鏈表的,所以cJSON在使用過程中大量的使用malloc
從堆中分配動態內存的,所以在使用完之后,應當及時調用下面的函數,清空cJSON指針所指向的內存,該函數也可用於刪除某一條數據:
(void) cJSON_Delete(cJSON *item);
注意:該函數刪除一條JSON數據時,如果有嵌套,會連帶刪除。
內存鈎子
cJSON在支持自定義malloc函數和free函數,方法如下:
- ① 使用
cJSON_Hooks
來連接自定義malloc函數和free函數:
typedef struct cJSON_Hooks { /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ void *(CJSON_CDECL *malloc_fn)(size_t sz); void (CJSON_CDECL *free_fn)(void *ptr); } cJSON_Hooks;
- ② 初始化鈎子cJSON_Hooks
(void) cJSON_InitHooks(cJSON_Hooks* hooks);