cJSON的目标是成为您能够完成工作的“最愚蠢(最便捷)”的解析器。它是一个C文件和一个头文件。
JSON它类似于XML,但不含冗余。您可以使用它来移动数据、存储数据,或者只是表示程序的状态。
作为一个库,cJSON的存在可以带走尽可能多的跑腿工作(重复造轮子),但不会妨碍您的工作。作为实用主义的观点(即忽略事实),我想说你可以在两种模式中使用它:自动模式和手动模式。让我们快速浏览一下。
有几种方法可以将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。
mkdir buildcd buildcmake ..
这将创建一个Makefile和一堆其他文件。然后你可以编译它:
make
如果你想安装的话,可以使用make install。默认情况下,它将标头/usr/local/include/cjson和库安装到/usr/local/lib。它还为pkg-config安装文件,以便更容易地检测和使用CMake的现有安装。它安装CMake配置文件,其他基于CMake的项目可以使用这些配置文件来发现库。
您可以使用可以传递给CMake的不同选项列表来更改构建过程,打开和关闭:
如果您正在为一个Linux发行版打包cJSON,您可能会采取以下步骤:
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: 在带有源代码的目录中运行这个命令,它将自动编译静态和共享库以及一个小测试程序(不是完整的测试套件)。
make all
如果需要,可以使用make install将编译后的库安装到系统中。默认情况下,它将在/usr/local/include/cjson中安装标头,在/usr/local/lib中安装库。但是您可以通过设置PREFIX和DESTDIR变量来更改此行为:make PREFIX=/usr DESTDIR=temp install。然后使用:make PREFIX=/usr DESTDIR=temp uninstall来卸载它们。
Vcpkg
你可以使用vcpkg依赖管理器下载和安装cJSON:
git clone https://github.com/Microsoft/vcpkg.gitcd vcpkg./bootstrap-vcpkg.sh./vcpkg integrate installvcpkg install cjson
vcpkg中的cJSON端口由Microsoft团队成员和社区贡献者保持最新。如果版本过期,请在vcpkg存储库中创建问题或拉出请求。
如果你通过CMake或Makefile安装它,你可以像这样包含cJSON:
#include <cjson/cJSON.h>
cJSON表示使用cJSON结构数据类型的JSON数据:
/* 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_Create…函数,可用于创建该类型的项。所有这些都将分配一个cJSON结构,稍后可以使用cJSON_Delete删除它。请注意,您必须在某个时候删除它们,否则将导致内存泄漏。
重要提示:如果您已经向数组或对象添加了项,则不能使用cJSON_Delete删除它。将其添加到数组或对象中会转移其所有权,以便在删除该数组或对象时也将其删除。您也可以使用cJSON_SetValuestring来更改cJSON_String的valuestring,而不必手动释放先前的valuestring。
基本类型
数组
您可以使用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解析它。
cJSON *json = cJSON_Parse(string);
给定一个字符串中的一些JSON(无论是否终止为0),您可以使用cJSON_ParseWithLength解析它。
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将它们打印为字符串。
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:
{ "name": "Awesome 4K", "resolutions": [ { "width": 1280, "height": 720 }, { "width": 1920, "height": 1080 }, { "width": 3840, "height": 2160 } ]}
输出
让我们构建上面的JSON并将其打印为一个字符串:
//创建一个具有受支持的列表的监视器//注意:返回一个堆分配的字符串,您需要在使用后释放它。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辅助功能,让我们的生活更轻松:
//注意:返回一个堆分配的字符串,您需要在使用后释放它。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,并检查监视器是否支持全高清分辨率,同时打印一些诊断输出:
/* 如果监视器支持全高清,返回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不是线程安全的。 但在以下情况下是线程安全的:
Case Sensitivity大小写敏感性
在最初创建cJSON时,它没有遵循JSON标准,也没有区分大写和小写字母。如果您想要正确的、标准的兼容的行为,您需要在可用的地方使用案例敏感函数。
Duplicate Object Members复制对象成员
cJSON支持解析和打印包含具有多个同名成员的对象的JSON。然而,cJSON_GetObjectItemCaseSensitive总是只返回第一个。