目录
- 为什么在 C 语言中处理 JSON?
- 主流 C 语言 JSON 库对比
- 开发环境准备
- 实战演练:使用
cJSON库- 1 解析 JSON 字符串
- 2 遍历和访问 JSON 数据
- 3 创建和生成 JSON
- 4 修改和删除 JSON 元素
- 5 释放内存
- 最佳实践与注意事项
为什么在 C 语言中处理 JSON?
C 语言作为一种系统级编程语言,以其高性能和底层控制能力著称,在以下场景中,你可能需要在 C 语言中处理 JSON:

- 嵌入式系统和物联网设备:资源受限的设备需要高效的 JSON 解析器来处理来自云平台或 API 的数据。
- 高性能服务器后端:对于追求极致性能的服务器,用 C 语言直接处理 JSON 可以避免其他语言解释器的开销。
- 系统工具和脚本:开发需要与 JSON 配置文件或网络 API 交互的命令行工具。
- 与现有 C/C++ 代码库集成:当你的核心逻辑是用 C/C++ 编写的,而外部数据交换格式是 JSON 时。
主流 C 语言 JSON 库对比
选择一个合适的库是成功的关键,以下是几个最流行的库及其特点:
| 库名 | 特点 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
cJSON |
单文件实现,轻量级,易于集成。 | 极易使用,API 简洁,文档和社区支持好,无依赖。 | 功能相对基础,性能不是顶尖。 | 初学者首选,嵌入式,快速原型开发。 |
JSMN |
单文件实现,流式解析器,非常轻量。 | 极致轻量,内存占用极小,速度快,支持流式处理。 | API 更底层,需要手动管理状态,功能比 cJSON 少。 |
内存极度受限的嵌入式系统。 |
Parson |
单文件实现,轻量级,API 风格类似 cJSON。 |
易于使用,API 设计友好,支持流式解析和生成。 | 社区和支持相比 cJSON 较小。 |
cJSON 的一个优秀替代品。 |
yajl (Yet Another JSON Library) |
C 库,也提供绑定(如 yajl-ruby)。 |
性能高,支持 SAX 风格(流式)和 DOM 风格解析。 | 依赖 CMake 构建,API 相对复杂。 | 对性能有高要求的服务端应用。 |
simdjson |
现代库,利用 SIMD 指令实现极速解析。 | 目前最快的 JSON 解析器之一,性能惊人。 | 依赖较新的 CPU 指令集(如 SSE4.2, AVX),API 与传统 DOM 模型不同。 | 需要处理海量 JSON 数据的高性能计算场景。 |
推荐:对于大多数开发者,cJSON 是最佳入门和通用选择,它简单、强大且足够快,本指南将以 cJSON 为例进行详细讲解。
开发环境准备
以 Linux (Ubuntu/Debian) 为例,Windows 用户可以使用 MinGW 或 MSYS2。
1 获取 cJSON 源码
cJSON 是单文件库,非常方便。

# 克隆 cJSON 的 GitHub 仓库 git clone https://github.com/DaveGamble/cJSON.git cd cJSON
2 编译 cJSON 库
cJSON 提供了一个 Makefile,可以方便地编译成一个静态库(.a 文件)。
# 编译生成 libcjson.a make
编译成功后,你会看到 libcjson.a 和 cJSON.h 文件。cJSON.h 是头文件,libcjson.a 是编译好的库文件。
3 项目结构
为了方便管理,建议你的项目结构如下:
my_json_project/
├── include/
│ └── cJSON.h # 从 cJSON 项目中复制过来
├── lib/
│ └── libcjson.a # 从 cJSON 项目中复制过来
└── src/
└── main.c # 你的 C 源代码
实战演练:使用 cJSON 库
我们将创建一个 main.c 文件,并编写代码来解析和生成 JSON。

1 解析 JSON 字符串
我们来看如何解析一个 JSON 字符串。
src/main.c
#include <stdio.h>
#include <stdlib.h>
#include "include/cJSON.h" // 包含头文件
int main() {
// 1. 准备一个 JSON 字符串
const char *json_string = "{"
"\"name\": \"John Doe\","
"\"age\": 30,"
"\"is_student\": false,"
"\"courses\": [\"Math\", \"Physics\", \"Computer Science\"],"
"\"address\": {"
"\"street\": \"123 Main St\","
"\"city\": \"New York\""
"}"
"}";
// 2. 解析 JSON 字符串
// cJSON_Parse 会分配内存,返回一个 cJSON 对象指针
cJSON *root = cJSON_Parse(json_string);
// 3. 检查解析是否成功
if (root == NULL) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
fprintf(stderr, "Error before: %s\n", error_ptr);
}
return 1;
}
printf("JSON parsing successful!\n");
// ... 在这里进行数据访问 ...
// 4. 释放内存 (非常重要!)
cJSON_Delete(root);
return 0;
}
2 遍历和访问 JSON 数据
解析成功后,我们可以通过 cJSON 提供的函数来访问数据。
在 main.c 中添加以下代码(在 printf 之后,cJSON_Delete 之前):
// 访问字符串 "name"
cJSON *name_item = cJSON_GetObjectItemCaseSensitive(root, "name");
if (cJSON_IsString(name_item) && (name_item->valuestring != NULL)) {
printf("Name: %s\n", name_item->valuestring);
}
// 访问数字 "age"
cJSON *age_item = cJSON_GetObjectItemCaseSensitive(root, "age");
if (cJSON_IsNumber(age_item)) {
printf("Age: %d\n", age_item->valueint); // valueint 用于获取整数值
// printf("Age (as double): %f\n", age_item->valuedouble); // valuedouble 用于获取浮点数值
}
// 访问布尔值 "is_student"
cJSON *is_student_item = cJSON_GetObjectItemCaseSensitive(root, "is_student");
if (cJSON_IsBool(is_student_item)) {
printf("Is Student: %s\n", cJSON_IsTrue(is_student_item) ? "true" : "false");
}
// 访问数组 "courses"
cJSON *courses_item = cJSON_GetObjectItemCaseSensitive(root, "courses");
if (cJSON_IsArray(courses_item)) {
printf("Courses: [");
cJSON *course = NULL;
cJSON_ArrayForEach(course, courses_item) {
if (cJSON_IsString(course)) {
printf("\"%s\"", course->valuestring);
if (course->next) { // 如果不是最后一个元素,打印逗号
printf(", ");
}
}
}
printf("]\n");
}
// 访问嵌套对象 "address"
cJSON *address_item = cJSON_GetObjectItemCaseSensitive(root, "address");
if (cJSON_IsObject(address_item)) {
cJSON *street_item = cJSON_GetObjectItemCaseSensitive(address_item, "street");
cJSON *city_item = cJSON_GetObjectItemCaseSensitive(address_item, "city");
if (cJSON_IsString(street_item) && cJSON_IsString(city_item)) {
printf("Address: %s, %s\n", street_item->valuestring, city_item->valuestring);
}
}
编译并运行:
# -I 指定头文件路径,-L 指定库文件路径,-lcjson 链接库 gcc src/main.c -I./include -L./lib -lcjson -o my_json_app # 运行 (可能需要设置 LD_LIBRARY_PATH 以便找到库文件) ./my_json_app
预期输出:
JSON parsing successful!
Name: John Doe
Age: 30
Is Student: false
Courses: ["Math", "Physics", "Computer Science"]
Address: 123 Main St, New York
3 创建和生成 JSON
cJSON 不仅可以解析,还可以从零开始构建 JSON。
在 main.c 中添加一个新的 main 函数或替换现有逻辑:
#include <stdio.h>
#include <stdlib.h>
#include "include/cJSON.h"
int main_create() {
// 1. 创建根对象 (一个 JSON 对象)
cJSON *root = cJSON_CreateObject();
// 2. 向对象中添加键值对
cJSON_AddStringToObject(root, "name", "Jane Doe");
cJSON_AddNumberToObject(root, "age", 28);
cJSON_AddBoolToObject(root, "is_student", cJSON_False); // 或 cJSON_True
// 3. 创建并填充数组
cJSON *courses = cJSON_CreateArray();
cJSON_AddItemToArray(courses, cJSON_CreateString("History"));
cJSON_AddItemToArray(courses, cJSON_CreateString("Literature"));
cJSON_AddItemToObject(root, "courses", courses); // 将数组添加到根对象
// 4. 创建嵌套对象
cJSON *address = cJSON_CreateObject();
cJSON_AddStringToObject(address, "street", "456 Oak Ave");
cJSON_AddStringToObject(address, "city", "Boston");
cJSON_AddItemToObject(root, "address", address);
// 5. 将 cJSON 对象转换为格式化的 JSON 字符串
// cJSON_Print 会进行美化,换行和缩进
char *json_formatted_string = cJSON_Print(root);
printf("Generated JSON (pretty-printed):\n%s\n", json_formatted_string);
// 6. 将 cJSON 对象转换为未格式化的 JSON 字符串 (更紧凑)
// cJSON_PrintUnformatted 不会添加多余的空格和换行
char *json_compact_string = cJSON_PrintUnformatted(root);
printf("Generated JSON (compact):\n%s\n", json_compact_string);
// 7. 释放字符串的内存 (cJSON_Print 分配了内存)
free(json_formatted_string);
free(json_compact_string);
// 8. 释放 cJSON 对象的内存
cJSON_Delete(root);
return 0;
}
编译并运行:
gcc src/main.c -I./include -L./lib -lcjson -o my_json_app ./my_json_app
预期输出:
Generated JSON (pretty-printed):
{
"name": "Jane Doe",
"age": 28,
"is_student": false,
"courses": [
"History",
"Literature"
],
"address": {
"street": "456 Oak Ave",
"city": "Boston"
}
}
Generated JSON (compact):
{"name":"Jane Doe","age":28,"is_student":false,"courses":["History","Literature"],"address":{"street":"456 Oak Ave","city":"Boston"}}
4 修改和删除 JSON 元素
修改和删除是解析和创建的结合。
// 假设我们已经有一个解析好的 root 对象 (从 4.1 节的 json_string 解析而来)
// ...
// 修改
cJSON *age_item = cJSON_GetObjectItemCaseSensitive(root, "age");
if (age_item) {
age_item->valueint = 31; // 修改整数值
age_item->valuedouble = 31.0; // 同时修改浮点数值
}
// 删除一个元素
cJSON_DeleteItemFromObjectCaseSensitive(root, "is_student");
// 在数组中删除一个元素
cJSON *courses_item = cJSON_GetObjectItemCaseSensitive(root, "courses");
if (courses_item && cJSON_IsArray(courses_item)) {
// 找到要删除的元素 ("Physics")
cJSON *course_to_delete = NULL;
cJSON_ArrayForEach(course_to_delete, courses_item) {
if (course_to_delete && cJSON_IsString(course_to_delete) && strcmp(course_to_delete->valuestring, "Physics") == 0) {
cJSON_DeleteItemFromArray(courses_item, course_to_delete); // 注意:这里需要传递索引,但 API 设计如此,通常需要先找到索引
break;
}
}
// 更简单的方法是遍历并删除匹配的项
int i;
for (i = cJSON_GetArraySize(courses_item) - 1; i >= 0; i--) {
cJSON *item = cJSON_GetArrayItem(courses_item, i);
if (item && cJSON_IsString(item) && strcmp(item->valuestring, "Physics") == 0) {
cJSON_DeleteItemFromArray(courses_item, i);
break;
}
}
}
// 生成并打印修改后的 JSON
char *modified_json = cJSON_Print(root);
printf("Modified JSON:\n%s\n", modified_json);
free(modified_json);
// ...
5 释放内存
这是最重要的一点! cJSON 的所有解析和创建函数都会在堆上分配内存,你必须手动释放这些内存,否则会导致内存泄漏。
- *`cJSON_Delete(cJSON item)
**: 递归删除一个cJSON` 对象及其所有子项,这是最常用的释放函数。 - *`free(char string)cJSON_Print
和cJSON_PrintUnformatted返回的字符串需要用free` 释放。
最佳实践与注意事项
- 始终检查返回值:
cJSON_Parse可能返回NULL,cJSON_GetObjectItem可能返回NULL,在访问返回的指针之前,务必检查它是否为空。 - 使用
CaseSensitive版本:cJSON_GetObjectItemCaseSensitive是更安全的选择,因为它可以避免因大小写不匹配导致的 bug,普通版本cJSON_GetObjectItem是大小写不敏感的。 - 善用类型检查宏:在访问数据之前,使用
cJSON_IsString,cJSON_IsNumber,cJSON_IsArray等宏来验证数据类型,防止程序崩溃。 - 错误处理:
cJSON_GetErrorPtr()可以在解析失败时提供出错的字符位置,对调试非常有帮助。 - 内存管理:遵循“谁分配,谁释放”的原则,解析的
cJSON树用cJSON_Delete释放,cJSON_Print产生的字符串用free释放。 - 线程安全:
cJSON本身不是线程安全的,如果你在多线程环境中使用,必须为每个线程创建独立的cJSON对象,或者在使用cJSON的函数时加锁。
本指南带你从零开始,学习了在 C 语言中使用 cJSON 库进行 JSON 开发的全过程。
- 选择库:
cJSON因其简单易用成为入门首选。 - 解析:使用
cJSON_Parse将字符串解析为cJSON对象树,然后用cJSON_GetObjectItem和cJSON_GetArrayItem遍历访问。 - 生成:使用
cJSON_CreateObject/cJSON_CreateArray创建结构,用cJSON_Add...ToObject添加数据,最后用cJSON_Print生成字符串。 - 核心:内存管理是 C 语言开发的重中之重,务必记得
cJSON_Delete和free。
掌握了这些基本操作后,你就可以在 C 语言项目中自如地处理 JSON 数据了,随着需求的增长,你还可以探索 cJSON 更高级的功能,如自定义内存分配器、处理二进制数据等。
