前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >cJSON,c语言的JSON库!

cJSON,c语言的JSON库!

作者头像
小锋学长生活大爆炸
发布2020-08-13 14:57:51
3.7K0
发布2020-08-13 14:57:51
举报

Welcome to cJSON.

cJSON的目标是成为您能够完成工作的“最愚蠢(最便捷)”的解析器。它是一个C文件和一个头文件。

JSON它类似于XML,但不含冗余。您可以使用它来移动数据、存储数据,或者只是表示程序的状态。

作为一个库,cJSON的存在可以带走尽可能多的跑腿工作(重复造轮子),但不会妨碍您的工作。作为实用主义的观点(即忽略事实),我想说你可以在两种模式中使用它:自动模式手动模式。让我们快速浏览一下。

Building

有几种方法可以将cJSON合并到您的项目中。

复制源文件

因为整个库只有一个C文件和一个头文件,所以您可以将cJSON.h和cJSON.c复制到您的项目源代码并开始使用它。 cJSON是用ANSI C (C89)编写的,以支持尽可能多的平台和编译器。

CMake

使用CMake, cJSON支持完整的构建系统。通过这种方式,您可以获得最多的功能。支持与2.8.5相同或更高版本的CMake。使用CMake时,建议执行out of tree构建,即将编译后的文件放在与源文件分开的目录中。因此,为了在Unix平台上使用CMake构建cJSON,需要创建一个构建目录并在其中运行CMake。

代码语言:javascript
复制
mkdir buildcd buildcmake ..

这将创建一个Makefile和一堆其他文件。然后你可以编译它:

代码语言:javascript
复制
make

如果你想安装的话,可以使用make install。默认情况下,它将标头/usr/local/include/cjson和库安装到/usr/local/lib。它还为pkg-config安装文件,以便更容易地检测和使用CMake的现有安装。它安装CMake配置文件,其他基于CMake的项目可以使用这些配置文件来发现库。

您可以使用可以传递给CMake的不同选项列表来更改构建过程,打开和关闭:

  • -DENABLE_CJSON_TEST=On:启用构建测试。(默认情况下)
  • -DENABLE_CJSON_UTILS=On:启用构建cJSON_Utils。(默认情况下)
  • -DENABLE_TARGET_EXPORT=On:启用CMake目标的导出。如果有问题就关掉。(默认情况下)
  • -DENABLE_CUSTOM_COMPILER_FLAGS=On:启用自定义编译器标记(目前适用于Clang、GCC和MSVC)。如果有问题就关掉。(默认情况下)
  • -DENABLE_VALGRIND=On:使用valgrind运行测试。(默认情况下)
  • -DENABLE_SANITIZERS=On:在启用了AddressSanitizer和UndefinedBehaviorSanitizer功能(如果可能的话)的情况下编译cJSON。(默认情况下)
  • -DENABLE_SAFE_STACK:启用SafeStack检测传递。目前只适用于Clang编译器。(默认情况下)
  • -DBUILD_SHARED_LIBS=On:构建共享库。(默认情况下)
  • -DBUILD_SHARED_AND_STATIC_LIBS=On:构建共享库和静态库。(默认情况下)
  • -DCMAKE_INSTALL_PREFIX=/usr:设置安装的前缀。
  • -DENABLE_LOCALES=On:启用localeconv方法。(默认开启)
  • -DCJSON_OVERRIDE_BUILD_SHARED_LIBS=On:启用- dcjson_build_build_shared_libs覆盖BUILD_SHARED_LIBS的值。

如果您正在为一个Linux发行版打包cJSON,您可能会采取以下步骤:

代码语言:javascript
复制
mkdir buildcd buildcmake .. -DENABLE_CJSON_UTILS=On -DENABLE_CJSON_TEST=Off -DCMAKE_INSTALL_PREFIX=/usrmakemake DESTDIR=$pkgdir install

在Windows上,CMake通常用于创建Visual Studio解决方案文件,方法是在Visual Studio的开发人员命令提示符中运行它,具体步骤遵循CMake和Microsoft的官方文档,并使用您选择的在线搜索引擎。上述选项的描述仍然普遍适用,尽管并非所有选项都适用于Windows。

Makefile

注意:不推荐使用此方法。尽可能使用CMake。Makefile支持仅限于修复bug。

如果你没有可用的CMake,但仍然有GNU make。您可以使用makefile来构建cJSON: 在带有源代码的目录中运行这个命令,它将自动编译静态和共享库以及一个小测试程序(不是完整的测试套件)。

代码语言:javascript
复制
make all

如果需要,可以使用make install将编译后的库安装到系统中。默认情况下,它将在/usr/local/include/cjson中安装标头,在/usr/local/lib中安装库。但是您可以通过设置PREFIXDESTDIR变量来更改此行为:make PREFIX=/usr DESTDIR=temp install。然后使用:make PREFIX=/usr DESTDIR=temp uninstall来卸载它们。

Vcpkg

你可以使用vcpkg依赖管理器下载和安装cJSON:

代码语言:javascript
复制
git clone https://github.com/Microsoft/vcpkg.gitcd vcpkg./bootstrap-vcpkg.sh./vcpkg integrate installvcpkg install cjson

vcpkg中的cJSON端口由Microsoft团队成员和社区贡献者保持最新。如果版本过期,请在vcpkg存储库中创建问题或拉出请求。

Including cJSON

如果你通过CMake或Makefile安装它,你可以像这样包含cJSON:

代码语言:javascript
复制
#include <cjson/cJSON.h>

Data Structure

cJSON表示使用cJSON结构数据类型的JSON数据:

代码语言:javascript
复制
/* cJSON结构: */typedef struct cJSON{    struct cJSON *next;    struct cJSON *prev;    struct cJSON *child;    int type;    char *valuestring;    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */    int valueint;    double valuedouble;    char *string;} cJSON;

这种类型的项表示JSON值。类型以位标志的形式存储在type中(这意味着仅通过比较type的值无法找到类型)。

要检查项的类型,请使用相应的cJSON_Is…函数。它执行一个NULL检查,然后执行一个类型检查,如果项目是这种类型,则返回一个布尔值。

可以是以下类型之一:

  • cJSON_Invalid(使用cJSON_IsInvalid进行检查):表示不包含任何值的无效项。如果将项设置为所有零字节,则会自动拥有此类型。
  • cJSON_False(用cJSON_IsFalse检查):表示一个假布尔值。您还可以使用cJSON_IsBool检查布尔值。
  • cJSON_True(用cJSON_IsTrue检查):表示一个真正的布尔值。您还可以使用cJSON_IsBool检查布尔值。
  • cJSON_NULL(使用cJSON_IsNull检查):表示一个空值。
  • cJSON_Number(用cJSON_IsNumber检查):表示一个数值。该值在valuedouble和valueint中存储为double。如果该数字超出了整数的范围,则INT_MAX或INT_MIN用于valueint。
  • cJSON_String(用cJSON_IsString检查):表示一个字符串值。它以零终止字符串的形式存储在valuestring中。
  • cJSON_Array(使用cJSON_IsArray检查):表示一个数组值。这是通过将child指向一个表示数组中值的cJSON项的链表来实现的。这些元素使用next和prev链接在一起,其中第一个元素有prev。next == NULL,最后一个元素next == NULL。
  • cJSON_Object(用cJSON_IsObject检查):表示一个对象值。对象的存储方式与数组相同,唯一的区别是对象中的项将键存储为字符串。
  • cJSON_Raw(使用cjson_w进行检查):表示以零结尾的字符数组形式存储在valuestring中的任何JSON类型。例如,这可以用来避免反复打印相同的静态JSON以节省性能。cJSON在解析时永远不会创建这种类型。还要注意,cJSON不会检查它是否是有效的JSON。

此外,还有以下两个标志:

  • cJSON_IsReference:指定子元素指向的项和/或valuestring不属于这个元素,它只是一个引用。所以cJSON_Delete和其他函数将只释放这个项目,而不是它的子/valuestring。
  • cJSON_StringIsConst:这意味着字符串指向一个常量字符串。这意味着cJSON_Delete和其他函数不会尝试释放字符串。

Working with the data structure

对于每个值类型都有一个cJSON_Create…函数,可用于创建该类型的项。所有这些都将分配一个cJSON结构,稍后可以使用cJSON_Delete删除它。请注意,您必须在某个时候删除它们,否则将导致内存泄漏。

重要提示:如果您已经向数组或对象添加了项,则不能使用cJSON_Delete删除它。将其添加到数组或对象中会转移其所有权,以便在删除该数组或对象时也将其删除。您也可以使用cJSON_SetValuestring来更改cJSON_String的valuestring,而不必手动释放先前的valuestring。

基本类型

  • null 是用cJSON_CreateNull创建的
  • booleans 是用cJSON_CreateTrue创建的,cJSON_CreateFalse或cJSON_CreateBool
  • numbers 是用cJSON_CreateNumber创建的。这将设置valuedouble和valueint。如果数字超出了整数的范围,则使用INT_MAX或INT_MIN来创建valueint
  • strings ,使用cJSON_CreateString(复制该字符串)或cJSON_CreateStringReference(直接指向该字符串)创建该字符串。这意味着valuestring不会被cJSON_Delete删除,您要对它的生存期负责,这对常量很有用)

数组

您可以使用cJSON_CreateArray创建一个空数组。cJSON_CreateArrayReference可以用来创建一个不“拥有”其内容的数组,所以它的内容不会被cJSON_Delete删除。

若要将项添加到数组中,请使用cJSON_AddItemToArray将项追加到末尾。使用cJSON_AddItemReferenceToArray可以将一个元素添加为另一个项、数组或字符串的引用。这意味着cJSON_Delete将不会删除那些项的子属性或valuestring属性,因此,如果它们已经在其他地方使用了,就不会发生重复释放。要在中间插入项,可以使用cJSON_InsertItemInArray。它将在给定的基于0的索引处插入一个项,并将所有现有项向右移动。

如果您想从一个给定索引的数组中取出一个项目并继续使用它,那么使用cJSON_DetachItemFromArray,它将返回分离的项目,所以一定要将它分配给一个指针,否则您将有内存泄漏。

删除项目是使用cJSON_DeleteItemFromArray完成的。它的工作原理类似于cJSON_DetachItemFromArray,但是通过cJSON_Delete删除分离的项目。

您还可以在适当的位置替换数组中的项。使用索引的cJSON_ReplaceItemInArray或使用给定元素指针的cJSON_ReplaceItemViaPointer。如果cJSON_ReplaceItemViaPointer失败,它将返回0。这在内部做的是分离旧项、删除它并在其位置插入新项。

要获得数组的大小,请使用cJSON_GetArraySize。使用cJSON_GetArrayItem获取给定索引处的元素。

因为数组存储为一个链表,通过迭代索引效率低下(O (n²)),所以你可以使用cJSON_ArrayForEach宏遍历一个数组在O (n)时间复杂度。

对象

您可以使用cJSON_CreateObject创建一个空对象。cJSON_CreateObjectReference可以用来创建一个不“拥有”其内容的对象,因此它的内容不会被cJSON_Delete删除。

要向对象添加项,请使用cJSON_AddItemToObject。使用cJSON_AddItemToObjectCS向名称为常量或引用(该项的键,cJSON结构中的字符串)的对象添加项,这样cJSON_Delete就不会释放它。使用cJSON_AddItemReferenceToArray可以将一个元素添加为另一个对象、数组或字符串的引用。这意味着cJSON_Delete将不会删除那些项的子属性或valuestring属性,因此,如果它们已经在其他地方使用了,就不会发生重复释放。

如果你想从一个对象中取出一个项目,使用cJSON_DetachItemFromObjectCaseSensitive,它将返回分离的项目,所以一定要把它分配到一个指针,否则你会有一个内存泄漏。

删除项目是用cJSON_DeleteItemFromObjectCaseSensitive完成的。它的工作原理类似于cJSON_DetachItemFromObjectCaseSensitive,后面跟着cJSON_Delete。

您还可以在适当的位置替换对象中的项。或者使用键使用cJSON_ReplaceItemInObjectCaseSensitive,或者使用cJSON_ReplaceItemViaPointer给出一个指向元素的指针。如果cJSON_ReplaceItemViaPointer失败,它将返回0。这在内部做的是分离旧项、删除它并在其位置插入新项。

要获得对象的大小,可以使用cJSON_GetArraySize,这是因为在内部对象是作为数组存储的。

如果你想访问对象中的一个项目,使用cJSON_GetObjectItemCaseSensitive。

要在对象上进行迭代,可以使用cJSON_ArrayForEach宏,方法与数组相同。

cJSON还提供了方便的帮助函数,用于快速创建新项并将其添加到对象中,如cJSON_AddNullToObject。它们返回指向新项的指针,如果失败则返回NULL。

解析JSON

给定以零结尾的字符串中的一些JSON,您可以使用cJSON_Parse解析它。

代码语言:javascript
复制
cJSON *json = cJSON_Parse(string);

给定一个字符串中的一些JSON(无论是否终止为0),您可以使用cJSON_ParseWithLength解析它。

代码语言:javascript
复制
cJSON *json = cJSON_ParseWithLength(string, buffer_length);

它将解析JSON并分配一个表示它的cJSON项树。一旦它返回,您将完全负责在与cJSON_Delete一起使用后对它进行释放。

cJSON_Parse使用的分配器默认是malloc和free,但是可以使用cJSON_InitHooks(全局)更改。

如果发生错误,可以使用cJSON_GetErrorPtr访问指向输入字符串中错误位置的指针。注意,这可能会在多线程场景中产生竞争条件,在这种情况下,最好使用cJSON_ParseWithOpts和return_parse_end。默认情况下,解析后的JSON之后的输入字符串中的字符不会被视为错误。

如果你想要更多的选项,使用cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_ended)。return_parse_end返回一个指针,指向输入字符串中的JSON结尾或错误发生的位置(从而以线程安全的方式替换cJSON_GetErrorPtr)。require_null_ended,如果设置为1,那么如果输入字符串包含JSON之后的数据,则会导致错误。

如果你想要更多的设置缓冲区长度的选项,可以使用cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_)

输出JSON

给定一个cJSON项树,您可以使用cJSON_Print将它们打印为字符串。

代码语言:javascript
复制
char *string = cJSON_Print(json);

它将分配一个字符串并将树的JSON表示形式打印到其中。一旦它返回,您就完全有责任在与分配器一起使用后重新分配它。(通常是免费的,取决于cJSON_InitHooks设置了什么)。

cJSON_Print将使用空白来打印格式。如果您想打印没有格式,使用cjson_printunformatting。

如果您对结果字符串的大小有一个大致的概念,那么您可以使用cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)。fmt是一个布尔值,用于打开和关闭空白格式。prebuffer指定用于打印的第一个缓冲区大小。cJSON_Print的第一个缓冲区大小是256字节。一旦打印耗尽空间,就会分配一个新的缓冲区,并在继续打印之前复制旧的缓冲区。

通过使用cjson_printpre(cJSON *item, char *buffer, const int length, const cJSON_bool格式)可以完全避免这些动态缓冲区分配。它接受一个缓冲区的指针打印到它的长度。如果达到该长度,打印将失败并返回0。如果成功,则返回1。注意,您应该提供比实际需要更多的5个字节,因为cJSON在估计所提供的内存是否足够时不是100%准确的。

举例

在这个例子中,我们想要构建和解析以下JSON:

代码语言:javascript
复制
{    "name": "Awesome 4K",    "resolutions": [        {            "width": 1280,            "height": 720        },        {            "width": 1920,            "height": 1080        },        {            "width": 3840,            "height": 2160        }    ]}

输出

让我们构建上面的JSON并将其打印为一个字符串:

代码语言:javascript
复制
//创建一个具有受支持的列表的监视器//注意:返回一个堆分配的字符串,您需要在使用后释放它。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;    }    /* 创建成功后,立即将其添加到监视器中     * 从而转移到它的指针的所有权 */    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辅助功能,让我们的生活更轻松:

代码语言:javascript
复制
//注意:返回一个堆分配的字符串,您需要在使用后释放它。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;}

解析

在这个例子中,我们将解析上述格式的JSON,并检查监视器是否支持全高清分辨率,同时打印一些诊断输出:

代码语言:javascript
复制
/* 如果监视器支持全高清,返回1,否则返回0 */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已经检查了空输入,所以只传播空值,如果输入为空,则cJSON_IsNumber和cJSON_IsString返回0。

警告

Zero Character零字符

cJSON不支持包含0字符'\0'或\u0000的字符串。这在当前API中是不可能的,因为字符串是零终止的。

Character Encoding字符编码

cJSON只支持UTF-8编码的输入。但在大多数情况下,它不会拒绝无效的UTF-8作为输入,只是按原样传播它。只要输入不包含无效的UTF-8,输出就始终是有效的UTF-8。

C StandardC标准

cJSON是用ANSI C(或C89, C90)编写的。如果编译器或C库不遵循这个标准,就不能保证正确的行为。 注意:ANSI C不是c++,所以它不应该用c++编译器来编译。不过,您可以使用C编译器编译它,并将它与您的c++代码链接起来。虽然使用c++编译器进行编译可能有效,但不能保证正确的行为。

Floating Point Numbers浮点数

除了IEEE754双精度浮点数外,cJSON不支持任何双精度实现。它可能仍然可以与其他实现一起工作,但是其中的bug将被认为是无效的。 cJSON支持的浮点文字的最大长度目前是63个字符。

Deep Nesting Of Arrays And Objects数组和对象的深度嵌套

cJSON不支持深度嵌套的数组和对象,因为这会导致堆栈溢出。为了防止这种情况,cJSON将深度限制为CJSON_NESTING_LIMIT,默认值为1000,但是可以在编译时更改。

Thread Safety线程安全性

一般来说,cJSON不是线程安全的。 但在以下情况下是线程安全的:

  • cJSON_GetErrorPtr从未被使用过(可以使用cjson_parse_end参数cJSON_ParseWithOpts)
  • cJSON_InitHooks只在任何线程中使用cJSON之前被调用。
  • 在所有对cJSON函数的调用返回之前,从未调用setlocale。

Case Sensitivity大小写敏感性

在最初创建cJSON时,它没有遵循JSON标准,也没有区分大写和小写字母。如果您想要正确的、标准的兼容的行为,您需要在可用的地方使用案例敏感函数。

Duplicate Object Members复制对象成员

cJSON支持解析和打印包含具有多个同名成员的对象的JSON。然而,cJSON_GetObjectItemCaseSensitive总是只返回第一个。

Enjoy cJSON!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Welcome to cJSON.
  • Building
  • Including cJSON
  • Data Structure
  • Working with the data structure
  • Enjoy cJSON!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档