可用于在 C 程序中解析和生成 JSON 数据,实现数据交换与状态存储。该项目核心为单个 C 文件和头文件,支持基本类型、数组、对象等 JSON 结构,采用 ANSI C 编写以确保跨平台兼容性。【此简介由AI生成】
cJSON
轻量级 JSON 解析器,基于 ANSI C 编写。
目录
许可
MIT 许可证
版权所有 (c) 2009-2017 Dave Gamble 及 cJSON 贡献者
本许可协议免费授权任何获取本软件及其相关文档文件(以下简称“软件”)的个人,无限制地使用该软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本,并允许获得软件的个人按照以下条件进行上述行为:
上文所述的版权声明和本许可声明应包含在软件的所有副本或实质性部分中。
本软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于适销性、特定用途的适用性和不侵犯性。在任何情况下,作者或版权持有者不对任何由本软件或其使用或与其他交易产生的索赔、损害或其他责任承担责任。
使用说明
欢迎使用 cJSON
cJSON 致力于成为最简单的解析器,能够满足您的需求。它由单个 C 文件和单个头文件组成。
JSON 的最佳描述在这里:http://www.json.org/ 它就像 XML,但是更为轻量级。您用它来传输数据、存储信息,或通常用来表示程序状态。
作为一个库,cJSON 的存在是为了尽可能减少您的劳动,但不会干扰您的工作。作为一个实用主义的角度(即忽略真相),我会说您可以在两种模式中使用它:自动模式和手动模式。让我们快速了解一下。
我从这个页面获取了一些 JSON:http://www.json.org/fatfree.html 那个页面启发我编写了 cJSON,这是一个尝试与 JSON 本身拥有相同哲学思想的解析器。简单、愚笨、不干扰。
构建项目
有几种方法可以将 cJSON 集成到您的项目中。
复制源码
由于整个库仅包含一个 C 文件和一个头文件,您可以直接将 cJSON.h 和 cJSON.c 复制到您的项目源码中并开始使用。
cJSON 使用 ANSI C (C89) 编写,以便支持尽可能多的平台和编译器。
mkdir build
cd build
cmake ..
这将生成一个 Makefile 和一系列其他文件。然后你可以进行编译:
make
若您希望安装,请使用 make install 命令。默认情况下,它将头文件安装至 /usr/local/include/cjson 目录,并将库文件安装至 /usr/local/lib 目录。此外,它还会安装用于 pkg-config 的文件,以便更容易地检测和使用已安装的 CMake。同时,它还会安装 CMake 配置文件,其他基于 CMake 的项目可以使用这些文件来发现库。
您可以通过传递给 CMake 的不同选项列表来更改构建过程。使用 On 开启选项,使用 Off 关闭选项:
-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(如果可能的话)。(默认关闭)-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_SHARED_LIBS覆盖BUILD_SHARED_LIBS的值。
如果您正在为 Linux 发行版打包 cJSON,您可能会按照以下步骤操作:
mkdir build
cd build
cmake .. -DENABLE_CJSON_UTILS=On -DENABLE_CJSON_TEST=Off -DCMAKE_INSTALL_PREFIX=/usr
make
make DESTDIR=$pkgdir install
在 Windows 系统中,通常使用 CMake 来创建 Visual Studio 的解决方案文件,这需要在 Visual Studio 的开发者命令提示符中运行 CMake。具体的步骤请遵循 CMake 和 Microsoft 的官方文档,并使用您喜欢的在线搜索引擎。尽管不是所有的选项在 Windows 上都适用,但上述选项的描述通常仍然适用。
Makefile
注意: 此方法已弃用。如果可能,请使用 CMake。Makefile 的支持仅限于修复错误。
如果您无法使用 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.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
vcpkg install cjson
cJSON 在 vcpkg 中的版本由 Microsoft 团队成员和社区贡献者保持更新。如果版本过时,请在 vcpkg 仓库上 创建一个 Issue 或 Pull Request。
包含 cJSON
如果您通过 CMake 或 Makefile 安装了它,可以这样包含 cJSON:
#include <cjson/cJSON.h>
数据结构
cJSON 使用 cJSON 结构体数据类型来表示 JSON 数据:
/* The cJSON structure: */
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检查):代表布尔值false。您也可以使用cJSON_IsBool检查布尔值。cJSON_True(通过cJSON_IsTrue检查):代表布尔值true。您也可以使用cJSON_IsBool检查布尔值。cJSON_NULL(通过cJSON_IsNull检查):代表null值。cJSON_Number(通过cJSON_IsNumber检查):代表数字值。该值以双精度浮点数存储在valuedouble和valueint中。如果数字超出整数的范围,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检查):代表对象值。对象以与数组相同的方式存储,唯一的区别是对象中的项目存储其键在string中。cJSON_Raw(通过cJSON_IsRaw检查):代表存储在valuestring中的任意类型的 JSON。这是一个空终止的字符数组。例如,这可以用来避免重复打印相同的静态 JSON,以节省性能。cJSON 解析时永远不会创建此类型。另外请注意,cJSON 不会检查它是否为有效的 JSON。
此外,还有以下两个标志:
cJSON_IsReference:指定child指向的项目和/或valuestring不归此项目所有,它只是一个引用。因此cJSON_Delete和其他函数只会释放此项目,而不会释放其child或valuestring。cJSON_StringIsConst:这意味着string指向一个常量字符串。这意味着cJSON_Delete和其他函数将不会尝试释放string。
使用数据结构
对于每个值类型,都有一个 cJSON_Create... 函数,可以用来创建该类型的项目。
这些函数都会分配一个 cJSON 结构,稍后可以调用 cJSON_Delete 进行删除。
请注意,您必须删除它们,否则会发生内存泄漏。
重要提示:如果您已经将一个项目添加到数组或对象中,那么您不允许使用 cJSON_Delete 删除它。将其添加到数组或对象中会转移其所有权,以便在删除该数组或对象时,它也会被删除。您还可以使用 cJSON_SetValuestring 更改 cJSON_String 的 valuestring,无需手动释放之前的 valuestring。
基础类型
- null 通过
cJSON_CreateNull创建 - 布尔值 通过
cJSON_CreateTrue、cJSON_CreateFalse或cJSON_CreateBool创建 - 数字 通过
cJSON_CreateNumber创建。这会设置valuedouble和valueint。如果数字超出整数的范围,INT_MAX或INT_MIN将用于valueint - 字符串 通过
cJSON_CreateString(复制字符串)或cJSON_CreateStringReference(直接指向字符串)创建。这意味着valuestring不会被cJSON_Delete删除,您需要负责其生命周期,这对于常量很有用
数组
您可以创建一个空数组,使用 cJSON_CreateArray。cJSON_CreateArrayReference 可以用来创建不 "拥有" 其内容的数组,因此其内容不会被 cJSON_Delete 删除。
要将项目添加到数组,使用 cJSON_AddItemToArray 添加到末尾。
使用 cJSON_AddItemReferenceToArray 可以将元素作为对另一个项目、数组或字符串的引用添加。这意味着 cJSON_Delete 不会删除该元素的 child 或 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 结构中的 string),因此不会被 cJSON_Delete 释放。
使用 cJSON_AddItemReferenceToArray 可以将元素作为对另一个对象、数组或字符串的引用添加。这意味着 cJSON_Delete 不会删除该元素的 child 或 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(无论是否以零结尾),你可以使用 cJSON_ParseWithLength 函数来解析它。
cJSON *json = cJSON_ParseWithLength(string, buffer_length);
它将解析 JSON,并分配一个表示它的 cJSON 条目树。一旦返回,你必须在使用完毕后负责用 cJSON_Delete 来释放它。
cJSON_Parse 使用的分配器默认是 malloc 和 free,但可以通过 cJSON_InitHooks 进行(全局)更改。
如果发生错误,可以使用 cJSON_GetErrorPtr 访问输入字符串中错误位置的指针。不过请注意,在多线程场景中这可能产生竞态条件,在这种情况下,最好使用带有 return_parse_end 的 cJSON_ParseWithOpts。
默认情况下,输入字符串中解析后的 JSON 后面的字符不会被视作错误。
如果你需要更多选项,请使用 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,当输入字符串在 JSON 后面包含数据时,它将被视为错误。
如果你需要提供缓冲区长度等更多选项,请使用 cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)。
打印 JSON
给定一个 cJSON 条目树,你可以使用 cJSON_Print 将它们打印成一个字符串。
char *string = cJSON_Print(json);
将为字符串分配空间,并将树的 JSON 表示形式打印到其中。一旦返回,您需要使用您的分配器在用完后完全负责释放它(通常是 free,具体取决于使用 cJSON_InitHooks 设置了什么)。
cJSON_Print 将打印带有空白格式的字符串。如果您想打印不带格式的字符串,请使用 cJSON_PrintUnformatted。
如果您对生成的字符串大致大小有一个粗略估计,可以使用 cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)。fmt 是一个布尔值,用于开启或关闭带有空白的格式化。prebuffer 指定了打印时最初使用的缓冲区大小。cJSON_Print 当前使用 256 字节作为其初始缓冲区大小。一旦打印耗尽空间,就会分配新的缓冲区,并将旧的内容复制过来,然后继续打印。
通过使用 cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format),可以完全避免动态缓冲区的分配。它接受一个指向要打印的缓冲区的指针及其长度。如果达到长度,打印将失败并返回 0。成功的情况下,返回 1。请注意,您应该提供实际所需字节多出 5 个字节的空间,因为 cJSON 在估计提供的内存是否足够时并不完全准确。
示例
在这个示例中,我们想要构建并解析以下 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
//NOTE: Returns a heap allocated string, you are required to free it after use.
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 transferring 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 辅助函数来让我们的工作变得更加轻松:
//NOTE: Returns a heap allocated string, you are required to free it after use.
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,并检查监视器是否支持全高清分辨率,同时输出一些诊断信息:
/* 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的结果之外,没有进行NULL检查,因为cJSON_GetObjectItemCaseSensitive已经检查了NULL输入,所以NULL值只是被传递,而cJSON_IsNumber和cJSON_IsString如果输入是NULL则返回0。
注意事项
零字符
cJSON不支持包含零字符'\0'或\u0000的字符串。这是因为在当前的API中字符串以零字符结尾,所以这是不可能的。
字符编码
cJSON仅支持UTF-8编码的输入。在大多数情况下,它不会拒绝无效的UTF-8作为输入,而是直接将其传递。只要输入不包含无效的UTF-8,输出始终是有效的UTF-8。
C标准
cJSON是用ANSI C(或C89,C90)编写的。如果您的编译器或C库不遵循此标准,无法保证正确的行为。
注意:ANSI C不是C++,因此不应使用C++编译器进行编译。您可以使用C编译器编译它,并与您的C++代码链接。尽管使用C++编译器可能会工作,但无法保证正确的行为。
浮点数
cJSON正式支持的double实现仅为IEEE754双精度浮点数。它可能仍然与其他实现一起工作,但这些实现的bug将被视为无效。
cJSON支持的浮点文字的最大长度目前为63个字符。
数组和对象的深度嵌套
cJSON不支持嵌套过深的数组和对象,因为这可能导致堆栈溢出。为了防止这种情况,cJSON将深度限制为CJSON_NESTING_LIMIT,默认为1000,但在编译时可以更改。
线程安全
一般来说,cJSON 不是线程安全的。
然而,在以下条件下它是线程安全的:
- 从未使用
cJSON_GetErrorPtr(cJSON_ParseWithOpts的return_parse_end参数可以替代) cJSON_InitHooks仅在在任何线程使用cJSON之前调用。- 在所有对cJSON函数的调用返回之前,从未调用过
setlocale。
大小写敏感性
cJSON最初创建时,没有遵循JSON标准,也没有区分大小写字母。如果您希望得到正确、符合标准的 behavior,您需要在使用可用的地方使用CaseSensitive函数。
对象成员的重复
cJSON支持解析和打印包含具有相同名称的多个成员的对象的JSON。然而,cJSON_GetObjectItemCaseSensitive始终只返回第一个。
欢享cJSON!
- Dave Gamble(原始作者)
- Max Bruckner和Alan Wang(当前维护者)
- 以及其他cJSON贡献者