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对于输入空指针有检查
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值