cJSON文檔解析
cJSON是一個輕量級且易於擴展的JSON解析開源庫。
github地址爲:https://github.com/DaveGamble/cJSON
-
安裝與使用
-
源碼安裝:將cJSON.c與cJSON.h這兩個文件複製入自己的工程項目中,通過
#include "cJSON.h"
就可以使用了 -
編譯安裝
-
#克隆cJSON庫的源碼 git clone https://github.com/DaveGamble/cJSON cd cJSON mkdir build cd build cmake .. #編譯源碼 make #將頭文件放入/usr/local/include/cjson文件夾中,庫放入/usr/local/lib,需要超級用戶的權限 sudo make install
-
通過
#include <cjson/cJSON.h>
進行使用
-
-
-
數據結構
cJSON使用
cJSON
結構體來存儲JSON數據/* cJSON結構 */ typedef struct cJSON { struct cJSON *next; struct cJSON *prev; struct cJSON *child; int type; char *valuestring; //不應該直接向valueint寫數據,而是使用cJSON_SetNumberValue函數進行賦值 int valueint; double valuedouble; char *string; } cJSON;
- 一個
cJSON
結構體存儲一個JSON的值。cJSON
結構體中的type
是指向JSON值的類型,同時是以bit-flag
的形式存儲,這意味着不能僅僅通過比較type
的值來判斷JSON值的類型。 - 可以使用
cJSON_Is...
函數來檢查cJSON
結構體存儲JSON的值的類型,它會對空指針進行檢查,同時返回一個布爾值來判斷否是該值。 cJSON
可能的值有:cJSON_Invalid
:無效值,沒有存儲任何的值。當成員全部清零時便是該值cJSON_False
:爲假的布爾值cJSON_True
:爲真的布爾值cJSON_NULL
:空值cJOSN_Number
:數值,既作爲雙精度浮點數存儲於valuedouble
,又作爲整型存儲於valueint
。如果數值超過了整型的範圍,valueint
將被賦值爲INT_MAX
或者INT_MIN
cJSON_String
:字符串,以’\0’的形式結尾,存儲於valuestring
cJSON_Array
:數組,通過cJSON
節點鏈表來存儲array值,每一個元素使用next
和prev
進行相互連接,第一個成員的prev
值爲NULL,最後一個成員的next
值爲NULL。同時使用成員child
指針來指向該鏈表cJSON_Object
:對象,與數組有相似的存儲方式,唯一不同的是對象會將鍵值放入成員string
中cJSON_Raw
:JSON格式的字符串,以’\0’結尾,存儲於valuestring
。在一次又一次的打印相同的JSON場景下,它能夠節省內存。cJSON不會在解析json格式的字符串時產生這種類型。值的注意的是cJSON庫不會檢查其值是否是合法的cJSON_IsReference
:成員child
指針或者valuestring
指向的節點並不屬於自己,自己僅僅是一個引用。因此,cJSON_Delete
和其他相關的函數只會釋放這個引用本身,而不會去釋放child
或者valuestring
cJSON_StringIsConst
:成員string
指向的是一個字面量,因此,cJSON_Delete
和其他相關的函數不會試圖去釋放string
的內存
- 一個
-
使用這些數據結構
對於每一種類型的值都有一個對應的函數
cJSON_Create...
來創建。這類函數會動態分配一個cJSON
的結構,所以需要在使用完後用cJSON_Delete
釋放掉內存,以避免內存泄漏。注意:當你已經把一個節點加入了一個數組或者對象,你就不能在用
cJSON_Delete
去釋放這個節點的內存了,當該數組或者對象被刪除時,這個節點也會被刪除-
創建基本類型的函數
null
:cJSON_CreateNull
booleans
:cJSON_CreateTrue
,cJSON_CreateFalse
或者cJSON_CreateBool
numbers
:cJSON_CreateNumber
strings
:cJSON_CreateString
-
數組
你可以使用
cJSON_CreateArray
函數來創建一個新的空數組。cJSON_CreateArrayReference
函數可以創建一個數組的引用,因爲它沒有屬於自己的內容,所以它的子元素不會被cJSON_Delete
給刪除使用
cJSON_AddItemToArray
函數可以在數組的最後增加元素。使用cJSON_AddItemReferenceToArray
函數將會增加一個元素去引用其他的節點,這就意味着cJSON_Delete
不會去刪除這個元素的child
或者valuestring
屬性,因此當這些屬性在其他地方使用的時候,不用擔心重複釋放內存的事情發生。使用cJSON_InsertItemInArray
函數可以將一個新元素插入數組中的0索引的位置,舊的元素的索引依次加1如果你想根據索引去移除數組中的一個元素並且繼續去使用它,可以使用
cJSON_DetachItemFromArray
函數,它將會返回被分離的數組。爲避免內存泄漏,確保將返回值賦值給一個指針當你需要替換數組中的某一個元素時,
cJSON_ReplaceItemInArray
函數使用索引的方式來進行替換。cJSON_ReplaceItemViaPointer
函數使用指向該元素的指針,同時如果失敗則會返回0。這兩個函數會分離出舊的元素並刪除它,同時在這個位置加入新的元素使用
cJSON_GetArraySize
函數得到數組的大小。使用cJSON_GetArrayItem
得到一個元素的索引因爲數組是以鏈表的方式進行存儲的,所以通過索引的方式進行遍歷效率是很低的( O(n^2) )。建議使用宏
cJSON_ArrayForEach
來遍歷數組,它具有時間複雜度爲( O(n) ) -
對象
你可以使用
cJSON_CreateObject
函數來創建一個新的空對象。cJSON_CreateObjectReference
函數可以創建一個對象的引用,因爲它沒有屬於自己的內容,所以它的子元素不會被cJSON_Delete
給刪除使用
cJSON_AddItemToObject
函數來增加一個元素到對象裏。使用cJSON_AddItemToObjectCS
函數來增加對象裏的元素時,使用的鍵值(結構體cJSON
中string
成員)是一個引用或者是字面量,因此它會被cJSON_Delete
給忽略。使用cJSON_AddItemReferenceToArray
函數將會增加一個元素去引用其他的節點,這就意味着cJSON_Delete
不會去刪除這個元素的child
或者valuestring
屬性,因此當這些屬性在其他地方使用的時候,不用擔心重複釋放內存的事情發生使用
cJSON_DetachItemFromObjectCaseSensitive
函數來從對象中分離出一個元素,從函數命名可以看出對於指定的鍵值是大小寫敏感的,它將會返回被分離的數組。爲避免內存泄漏,確保將返回值賦值給一個指針使用
cJSON_DeleteItemFromObjectCaseSensitive
函數來從一個對象中刪除一個元素,可以把它看成先從對象中分離出該元素然後在刪除當你需要替換對象中的某一個元素時,
cJSON_ReplaceItemInObjectCaseSensitive
函數使用j鍵值查找的方式來進行替換。cJSON_ReplaceItemViaPointer
函數使用指向該元素的指針來查找並替換,同時如果失敗則會返回0。這兩個函數會分離出舊的元素並刪除它,同時在這個位置加入新的元素因爲對象的存儲方式和數組很像,所以同樣可以通過
cJSON_GetArraySize
來得到對象裏元素的個數使用
cJSON_GetObjectItemCaseSensitive
來訪問對象中的某一個元素使用宏
cJSON_ArrayForEach
來遍歷一個對象cJSON
同樣也提供便利的工具函數來快速的在對象內部創建一個新的元素,比如說cJSON_AddNullToObject
函數將會返回新加的元素指針,如果失敗則返回NULL
-
-
解析JSON字符串
可以使用
cJSON_Parse
函數來一些以’\0’結尾的字符串進行解析cJSON *json = cJSON_Parse(string);
解析後的結果是
cJSON
的樹狀的數據結構,一旦解析成功,就有責任在使用完後使用cJSON_Delete
釋放內存默認分配內存使用的是
malloc
函數,釋放內存使用的是free
函數。但是可以使用cJSON_InitHooks
函數來全局性改變當一個錯誤發生時,
cJSON_GetErrorPtr
函數可以得到指向輸入字符串中錯誤的位置的指針。值的注意的是在多線程的情況下,該函數會產生競爭條件,更好的方法是使用帶有return_parse_end
參數的cJSON_ParseWithOpts
函數。如果你想有更多的選項,使用
cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
函數,return_parse_end
返回輸入的JSON字符串的結尾或者一個錯誤發生的地方(從而在保障線程安全的情況下替換cJSON_GetErrorPtr
函數)。require_null_terminated
如果該值設爲1,那麼當輸入的字符串在有效的以’\0’結尾的json字符串後還包含其他的數據,就會報錯 -
打印JSON
使用
cJSON_Print
函數將一個cJSON
數據結構打印爲字符串char *string = cJSON_Print(json);
該函數將會動態分配內存給一個字符串,將JSON表達式放入其中。一旦該函數返回,就有責任釋放該內存(默認是
free
,取決於設置的cJSON_InitHooks
)cJSON_Print
將會用空白符來格式化JSON字符串。可以使用cJSON_PrintUnformatted
來無格式化的打印如果你關於返回的結果的字符串的大小有一個想法,你可以使用
cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)
函數。fmt
是一個決定是否用空白字符格式化JSON字符串,prebuffer
指出了所用的第一個緩衝區大小。cJOSN_Print
當前使用256字節的緩衝區大小。一旦打印超過了大小,新的緩衝區會被動態分配,在繼續打印之前舊的緩衝區裏的內容複製到新的緩衝區裏。使用
cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)
函數可以完全避免動態的內存分配,該函數需要指向緩衝區的指針和該緩衝區的大小,如果緩衝區過小,打印將會失敗,函數返回0。一旦成功,函數返回1。值得注意的是需要準備超過實際需要的字節還要多5個字節,因爲cJSON並不是100%的精確估計提供的內存是否足夠 -
示例
-
在這個例子裏,我們想去構建一個JSON並解析它
{ "name": "Awesome 4K", "resolutions": [ { "width": 1280, "height": 720 }, { "width": 1920, "height": 1080 }, { "width": 3840, "height": 2160 } ] }
-
構建以上的json,然後打印成字符串
//create a monitor with a list of supported resolutions char* create_monitor(void) { const unsigned int resolution_numbers[3][2] = { {1280, 720}, {1920, 1080}, {3840, 2160} }; char *string = NULL; cJSON *name = NULL; cJSON *resolutions = NULL; cJSON *resolution = NULL; cJSON *width = NULL; cJSON *height = NULL; size_t index = 0; cJSON *monitor = cJSON_CreateObject(); if (monitor == NULL) { goto end; } name = cJSON_CreateString("Awesome 4K"); if (name == NULL) { goto end; } /* after creation was successful, immediately add it to the monitor, * thereby transfering ownership of the pointer to it */ cJSON_AddItemToObject(monitor, "name", name); resolutions = cJSON_CreateArray(); if (resolutions == NULL) { goto end; } cJSON_AddItemToObject(monitor, "resolutions", resolutions); for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index) { resolution = cJSON_CreateObject(); if (resolution == NULL) { goto end; } cJSON_AddItemToArray(resolutions, resolution); width = cJSON_CreateNumber(resolution_numbers[index][0]); if (width == NULL) { goto end; } cJSON_AddItemToObject(resolution, "width", width); height = cJSON_CreateNumber(resolution_numbers[index][1]); if (height == NULL) { goto end; } cJSON_AddItemToObject(resolution, "height", height); } string = cJSON_Print(monitor); if (string == NULL) { fprintf(stderr, "Failed to print monitor.\n"); } end: cJSON_Delete(monitor); return string; }
-
我們可以使用
cJSON_Add...ToObject
輔助函數來更方便構建char *create_monitor_with_helpers(void) { const unsigned int resolution_numbers[3][2] = { {1280, 720}, {1920, 1080}, {3840, 2160} }; char *string = NULL; cJSON *resolutions = NULL; size_t index = 0; cJSON *monitor = cJSON_CreateObject(); if (cJSON_AddStringToObject(monitor, "name", "Awesome 4K") == NULL) { goto end; } resolutions = cJSON_AddArrayToObject(monitor, "resolutions"); if (resolutions == NULL) { goto end; } for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index) { cJSON *resolution = cJSON_CreateObject(); if (cJSON_AddNumberToObject(resolution, "width", resolution_numbers[index][0]) == NULL) { goto end; } if(cJSON_AddNumberToObject(resolution, "height", resolution_numbers[index][1]) == NULL) { goto end; } cJSON_AddItemToArray(resolutions, resolution); } string = cJSON_Print(monitor); if (string == NULL) { fprintf(stderr, "Failed to print monitor.\n"); } end: cJSON_Delete(monitor); return string; }
-
解析並測試
/* return 1 if the monitor supports full hd, 0 otherwise */ int supports_full_hd(const char * const monitor) { const cJSON *resolution = NULL; const cJSON *resolutions = NULL; const cJSON *name = NULL; int status = 0; cJSON *monitor_json = cJSON_Parse(monitor); if (monitor_json == NULL) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { fprintf(stderr, "Error before: %s\n", error_ptr); } status = 0; goto end; } name = cJSON_GetObjectItemCaseSensitive(monitor_json, "name"); if (cJSON_IsString(name) && (name->valuestring != NULL)) { printf("Checking monitor \"%s\"\n", name->valuestring); } resolutions = cJSON_GetObjectItemCaseSensitive(monitor_json, "resolutions"); cJSON_ArrayForEach(resolution, resolutions) { cJSON *width = cJSON_GetObjectItemCaseSensitive(resolution, "width"); cJSON *height = cJSON_GetObjectItemCaseSensitive(resolution, "height"); if (!cJSON_IsNumber(width) || !cJSON_IsNumber(height)) { status = 0; goto end; } if ((width->valuedouble == 1920) && (height->valuedouble == 1080)) { status = 1; goto end; } } end: cJSON_Delete(monitor_json); return status; }
- 值的注意的是,對於
cJSON_Parse
函數的返回結果沒有空指針的檢查,因爲cJSON_GetObjectItemCaseSensitive
對於輸入空指針有檢查
- 值的注意的是,對於
-