cJSON文檔解析

cJSON文檔解析

cJSON是一個輕量級且易於擴展的JSON解析開源庫。

github地址爲:https://github.com/DaveGamble/cJSON

  • 安裝與使用

    • 源碼安裝:將cJSON.c與cJSON.h這兩個文件複製入自己的工程項目中,通過#include "cJSON.h"就可以使用了

    • 編譯安裝

      1. #克隆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
        
      2. 通過#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值,每一個元素使用nextprev進行相互連接,第一個成員的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去釋放這個節點的內存了,當該數組或者對象被刪除時,這個節點也會被刪除

    • 創建基本類型的函數

      • nullcJSON_CreateNull
      • booleanscJSON_CreateTruecJSON_CreateFalse或者cJSON_CreateBool
      • numberscJSON_CreateNumber
      • stringscJSON_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函數來增加對象裏的元素時,使用的鍵值(結構體cJSONstring成員)是一個引用或者是字面量,因此它會被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對於輸入空指針有檢查
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章