对于C语言开发者而言,处理JSON格式数据常常是一个痛点。无论是调用RESTful API、解析配置文件,还是与其他系统进行数据交换,面对{"key": "value"}这样的结构,C语言标准库并未提供原生支持。手动解析字符串不仅繁琐易错,而且难以维护。
为此,cJSON库应运而生。它定位于成为“能帮你完成工作的、最简单的解析器”,凭借极简的设计哲学,成为C语言生态中广受欢迎的JSON库。
cJSON的核心优势
- 超轻量级:整个库仅包含
cJSON.c和cJSON.h两个文件,无需复杂构建系统。
- 高兼容性:采用ANSI C (C89)标准编写,兼容几乎所有编译环境和平台。
- API直观:设计简单直接,核心功能聚焦于JSON的解析与生成。
极简集成:三步引入项目
与许多需要复杂配置的库不同,cJSON的集成过程异常简单:
- 从GitHub获取源码或直接复制
cJSON.h与cJSON.c文件。
- 将这两个文件放入您的项目目录。
- 在源代码中包含头文件:
#include "cJSON.h"。
如果文件位于子目录,如libs/cjson/,则包含路径相应修改即可。集成完毕,您可以立即开始使用。
实战一:动态创建JSON对象
在cJSON中,所有JSON元素(对象、数组、字符串、数字等)均由cJSON结构体表示,如同统一的“乐高积木”。
| 常用创建函数 |
JSON元素 |
创建函数 |
对象 {} |
cJSON_CreateObject() |
数组 [] |
cJSON_CreateArray() |
字符串 "text" |
cJSON_CreateString("text") |
数字 123 |
cJSON_CreateNumber(123) |
布尔值 true |
cJSON_CreateTrue() |
空值 null |
cJSON_CreateNull() |
示例:构建显示器信息JSON
假设需要生成如下JSON,描述显示器及其支持的分辨率:
{
"name": "Awesome 4K",
"resolutions": [
{"width": 1280, "height": 720},
{"width": 1920, "height": 1080},
{"width": 3840, "height": 2160}
]
}
实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
char* create_monitor_json(void) {
// 1. 创建根对象
cJSON *monitor = cJSON_CreateObject();
// 2. 添加名称字段
cJSON_AddStringToObject(monitor, "name", "Awesome 4K");
// 3. 创建并添加分辨率数组
cJSON *resolutions = cJSON_AddArrayToObject(monitor, "resolutions");
// 4. 向数组中添加分辨率对象
int res_data[3][2] = {{1280, 720}, {1920, 1080}, {3840, 2160}};
for (int i = 0; i < 3; i++) {
cJSON *res = cJSON_CreateObject();
cJSON_AddNumberToObject(res, "width", res_data[i][0]);
cJSON_AddNumberToObject(res, "height", res_data[i][1]);
cJSON_AddItemToArray(resolutions, res); // 所有权转移
}
// 5. 将对象树转换为格式化字符串
char *json_str = cJSON_Print(monitor);
// 6. 释放cJSON对象树
cJSON_Delete(monitor);
return json_str; // 调用者需负责释放此字符串
}
int main(void) {
char *result = create_monitor_json();
printf("%s\n", result);
free(result); // 释放cJSON_Print分配的字符串
return 0;
}
对象构建流程

字符串输出函数
cJSON_Print(): 生成格式化的、带缩进的字符串,便于阅读和调试。
cJSON_PrintUnformatted(): 生成紧凑型字符串,节省空间,适合网络传输。
重要提示:cJSON_Print()系列函数在堆上分配内存,返回的字符串使用后必须调用free()释放,否则会导致内存泄漏。
实战二:解析JSON字符串
当接收到JSON字符串时,使用cJSON_Parse()进行解析。
安全解析
cJSON_Parse()在解析失败时会返回NULL,因此必须检查返回值。
cJSON *root = cJSON_Parse(json_string);
if (root == NULL) {
const char *error = cJSON_GetErrorPtr();
printf("JSON解析错误:%s\n", error);
// 错误处理...
}
数据提取“三板斧”
- 安全访问成员:使用
cJSON_GetObjectItemCaseSensitive(),该函数即使传入NULL对象也不会崩溃,支持安全链式调用。
cJSON *city = cJSON_GetObjectItemCaseSensitive(
cJSON_GetObjectItemCaseSensitive(root, "address"),
"city");
- 类型检查:提取值后,应先判断其类型,这是防御性编程的良好实践。
cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "name");
if (cJSON_IsString(name) && name->valuestring != NULL) {
printf("名称: %s\n", name->valuestring);
}
- 遍历数组:使用
cJSON_ArrayForEach宏可以像普通循环一样遍历JSON数组。
cJSON *res_item = NULL;
cJSON *resolutions = cJSON_GetObjectItemCaseSensitive(root, "resolutions");
cJSON_ArrayForEach(res_item, resolutions) {
// 处理每个数组元素res_item
}
完整示例:检查显示器是否支持1080P分辨率
#include <stdio.h>
#include "cJSON.h"
int supports_full_hd(const char *json_str) {
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
printf("JSON解析失败\n");
return 0;
}
cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "name");
if (cJSON_IsString(name)) {
printf("检查设备: %s\n", name->valuestring);
}
int found = 0;
cJSON *resolutions = cJSON_GetObjectItemCaseSensitive(root, "resolutions");
cJSON *res = NULL;
cJSON_ArrayForEach(res, resolutions) {
cJSON *w = cJSON_GetObjectItemCaseSensitive(res, "width");
cJSON *h = cJSON_GetObjectItemCaseSensitive(res, "height");
if (cJSON_IsNumber(w) && cJSON_IsNumber(h)) {
if (w->valuedouble == 1920 && h->valuedouble == 1080) {
found = 1;
break;
}
}
}
cJSON_Delete(root); // 释放整个对象树
return found;
}
关键:理解cJSON的内存管理规则
在C语言中使用动态内存分配库,明确内存所有权至关重要。
原则一:谁创建(或解析),谁释放
无论是通过cJSON_Parse()解析得到的,还是通过cJSON_CreateObject()等函数创建的顶层对象,在使用完毕后都必须调用cJSON_Delete()进行释放。该函数会递归释放对象及其所有子节点。
cJSON *root = cJSON_Parse(json_str);
// ... 使用root ...
cJSON_Delete(root); // 必须释放
原则二:所有权转移(避免重复释放)
当使用cJSON_AddItemToObject()或cJSON_AddItemToArray()将一个子节点添加到父节点后,该子节点的内存管理责任就转移给了父节点。此后,只能通过释放父节点来间接释放子节点,切勿再单独释放子节点,否则会导致“重复释放”(double free)错误。
// 正确做法
cJSON *root = cJSON_CreateObject();
cJSON *child = cJSON_CreateString("hello");
cJSON_AddItemToObject(root, "msg", child); // child的所有权转移给root
// ... 使用 ...
cJSON_Delete(root); // 一次性释放root及其所有子项(包括child)
// 错误做法:导致重复释放和程序崩溃
// cJSON_Delete(child); // 错误!所有权已转移
// cJSON_Delete(root); // 这里会再次尝试释放child
内存所有权转移示意图

总结与核心API速查
cJSON如同C语言开发者的瑞士军刀,尤其适合在资源受限的嵌入式环境或追求极致轻量化的场景中使用。
黄金法则:牢记“Add之后勿Delete,顶层对象必须删”,可规避绝大多数内存问题。
| 核心API速查表 |
功能 |
函数 |
| 解析字符串 |
cJSON_Parse() |
| 创建对象/数组 |
cJSON_CreateObject()/cJSON_CreateArray() |
| 向对象添加成员 |
cJSON_AddStringToObject()等 |
| 从对象获取成员 |
cJSON_GetObjectItemCaseSensitive() |
| 遍历数组 |
cJSON_ArrayForEach 宏 |
| 类型判断 |
cJSON_IsString(), cJSON_IsNumber()等 |
| 生成字符串 |
cJSON_Print(), cJSON_PrintUnformatted() |
| 释放内存 |
cJSON_Delete() |
通过cJSON,C语言开发者可以轻松应对现代网络通信与数据交换中的JSON处理需求,告别手写解析器的时代。项目开源地址:https://github.com/DaveGamble/cJSON。